diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md new file mode 100644 index 000000000..cc53dac0f --- /dev/null +++ b/.claude/rules/architecture.md @@ -0,0 +1,28 @@ +# Architecture + +``` +codeflash/ +├── main.py # CLI entry point +├── cli_cmds/ # Command handling, console output (Rich) +├── discovery/ # Find optimizable functions +├── context/ # Extract code dependencies and imports +├── optimization/ # Generate optimized code via AI +│ ├── optimizer.py # Main optimization orchestration +│ └── function_optimizer.py # Per-function optimization logic +├── verification/ # Run deterministic tests (pytest plugin) +├── benchmarking/ # Performance measurement +├── github/ # PR creation +├── api/ # AI service communication +├── code_utils/ # Code parsing, git utilities +├── models/ # Pydantic models and types +├── languages/ # Multi-language support (Python, JavaScript/TypeScript) +├── setup/ # Config schema, auto-detection, first-run experience +├── picklepatch/ # Serialization/deserialization utilities +├── tracing/ # Function call tracing +├── tracer.py # Root-level tracer entry point for profiling +├── lsp/ # IDE integration (Language Server Protocol) +├── telemetry/ # Sentry, PostHog +├── either.py # Functional Result type for error handling +├── result/ # Result types and handling +└── version.py # Version information +``` diff --git a/.claude/rules/code-style.md b/.claude/rules/code-style.md new file mode 100644 index 000000000..fcad0f253 --- /dev/null +++ b/.claude/rules/code-style.md @@ -0,0 +1,9 @@ +# Code Style + +- **Line length**: 120 characters +- **Python**: 3.9+ syntax +- **Tooling**: Ruff for linting/formatting, mypy strict mode, prek for pre-commit checks +- **Comments**: Minimal - only explain "why", not "what" +- **Docstrings**: Do not add unless explicitly requested +- **Naming**: NEVER use leading underscores (`_function_name`) - Python has no true private functions, use public names +- **Paths**: Always use absolute paths, handle encoding explicitly (UTF-8) diff --git a/.claude/rules/git.md b/.claude/rules/git.md new file mode 100644 index 000000000..058e8ca80 --- /dev/null +++ b/.claude/rules/git.md @@ -0,0 +1,6 @@ +# Git Commits & Pull Requests + +- Use conventional commit format: `fix:`, `feat:`, `refactor:`, `docs:`, `test:`, `chore:` +- Keep commits atomic - one logical change per commit +- Commit message body should be concise (1-2 sentences max) +- PR titles should also use conventional format diff --git a/.claude/rules/source-code.md b/.claude/rules/source-code.md new file mode 100644 index 000000000..27c939642 --- /dev/null +++ b/.claude/rules/source-code.md @@ -0,0 +1,11 @@ +--- +paths: + - "codeflash/**/*.py" +--- + +# Source Code Rules + +- Use `libcst` for code modification/transformation to preserve formatting. `ast` is acceptable for read-only analysis and parsing. +- NEVER use leading underscores for function names (e.g., `_helper`). Python has no true private functions. Always use public names. +- Any new feature or bug fix that can be tested automatically must have test cases. +- If changes affect existing test expectations, update the tests accordingly. Tests must always pass after changes. diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 000000000..809a4ea91 --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,15 @@ +--- +paths: + - "tests/**" + - "codeflash/**/*test*.py" +--- + +# Testing Conventions + +- Code context extraction and replacement tests must always assert for full string equality, no substring matching. +- Use pytest's `tmp_path` fixture for temp directories (it's a `Path` object). +- Write temp files inside `tmp_path`, never use `NamedTemporaryFile` (causes Windows file contention). +- Always call `.resolve()` on Path objects to ensure absolute paths and resolve symlinks. +- Use `.as_posix()` when converting resolved paths to strings (normalizes to forward slashes). +- Any new feature or bug fix that can be tested automatically must have test cases. +- If changes affect existing test expectations, update the tests accordingly. Tests must always pass after changes. diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 1ae5efec4..5c89e6ea7 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -54,20 +54,34 @@ jobs: PR NUMBER: ${{ github.event.pull_request.number }} EVENT: ${{ github.event.action }} - ## STEP 1: Run pre-commit checks and fix issues + ## STEP 1: Run prek and mypy checks, fix issues - First, run `uv run prek run --from-ref origin/main` to check for linting/formatting issues on files changed in this PR. + First, run these checks on files changed in this PR: + 1. `uv run prek run --from-ref origin/main` - linting/formatting issues + 2. `uv run mypy ` - type checking issues - If there are any issues: + If there are prek issues: - For SAFE auto-fixable issues (formatting, import sorting, trailing whitespace, etc.), run `uv run prek run --from-ref origin/main` again to auto-fix them + - For issues that prek cannot auto-fix, do NOT attempt to fix them manually — report them as remaining issues in your summary + + If there are mypy issues: + - Fix type annotation issues (missing return types, Optional/None unions, import errors for type hints, incorrect types) + - Do NOT add `type: ignore` comments - always fix the root cause + + After fixing issues: - Stage the fixed files with `git add` - - Commit with message "style: auto-fix linting issues" + - Commit with message "style: auto-fix linting issues" or "fix: resolve mypy type errors" as appropriate - Push the changes with `git push` + IMPORTANT - Verification after fixing: + - After committing fixes, run `uv run prek run --from-ref origin/main` ONE MORE TIME to verify all issues are resolved + - If errors remain, either fix them or report them honestly as unfixed in your summary + - NEVER claim issues are fixed without verifying. If you cannot fix an issue, say so + Do NOT attempt to fix: - - Type errors that require logic changes - - Complex refactoring suggestions - - Anything that could change behavior + - Type errors that require logic changes or refactoring + - Complex generic type issues + - Anything that could change runtime behavior ## STEP 2: Review the PR @@ -85,7 +99,6 @@ jobs: - Only create NEW inline comments for HIGH-PRIORITY issues found in changed files. - Limit to 5-7 NEW comments maximum per review. - Use CLAUDE.md for project-specific guidance. - - Use `gh pr comment` for summary-level feedback. - Use `mcp__github_inline_comment__create_inline_comment` sparingly for critical code issues only. ## STEP 3: Coverage analysis @@ -122,7 +135,45 @@ jobs: - New implementations/files: Must have ≥75% test coverage - Modified code: Changed lines should be exercised by existing or new tests - No coverage regressions: Overall coverage should not decrease - claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api:*),Bash(uv run prek *),Bash(uv run coverage *),Bash(uv run pytest *),Bash(git status*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git diff *),Bash(git checkout *),Read,Glob,Grep"' + + ## STEP 4: Post ONE consolidated summary comment + + CRITICAL: You must post exactly ONE summary comment containing ALL results (pre-commit, review, coverage). + DO NOT post multiple separate comments. Use this format: + + ``` + ## PR Review Summary + + ### Prek Checks + [status and any fixes made] + + ### Code Review + [critical issues found, if any] + + ### Test Coverage + [coverage table and analysis] + + --- + *Last updated: * + ``` + + To ensure only ONE comment exists: + 1. Find existing claude[bot] comment: `gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | select(.user.login == "claude[bot]") | .id' | head -1` + 2. If found, UPDATE it: `gh api --method PATCH repos/${{ github.repository }}/issues/comments/ -f body=""` + 3. If not found, CREATE: `gh pr comment ${{ github.event.pull_request.number }} --body ""` + 4. Delete any OTHER claude[bot] comments to clean up duplicates: `gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | select(.user.login == "claude[bot]") | .id' | tail -n +2 | xargs -I {} gh api --method DELETE repos/${{ github.repository }}/issues/comments/{}` + + ## STEP 5: Merge pending codeflash optimization PRs + + Check for open optimization PRs from codeflash and merge if CI passes: + + 1. List open PRs from codeflash bot: + `gh pr list --author "codeflash-ai[bot]" --state open --json number,title,headRefName` + + 2. For each optimization PR: + - Check if CI is passing: `gh pr checks ` + - If all checks pass, merge it: `gh pr merge --squash --delete-branch` + claude_args: '--model claude-opus-4-6 --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh pr checks:*),Bash(gh pr merge:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api:*),Bash(uv run prek *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(uv run pytest *),Bash(git status*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git diff *),Bash(git checkout *),Read,Glob,Grep,Edit"' additional_permissions: | actions: read env: @@ -194,7 +245,7 @@ jobs: uses: anthropics/claude-code-action@v1 with: use_foundry: "true" - claude_args: '--allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(git merge*),Bash(git fetch*),Bash(git checkout*),Bash(git branch*),Bash(uv run prek *),Bash(prek *),Bash(uv run ruff *),Bash(uv run pytest *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(gh pr comment*),Bash(gh pr view*),Bash(gh pr diff*),Bash(gh pr merge*),Bash(gh pr close*)"' + claude_args: '--model claude-opus-4-6 --allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(git merge*),Bash(git fetch*),Bash(git checkout*),Bash(git branch*),Bash(uv run prek *),Bash(prek *),Bash(uv run ruff *),Bash(uv run pytest *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(gh pr comment*),Bash(gh pr view*),Bash(gh pr diff*),Bash(gh pr merge*),Bash(gh pr close*)"' additional_permissions: | actions: read env: diff --git a/.gitignore b/.gitignore index b113ddf98..a22143bf8 100644 --- a/.gitignore +++ b/.gitignore @@ -264,6 +264,10 @@ WARP.MD .mcp.json .tessl/ tessl.json + +# Claude Code - track shared rules, ignore local config +.claude/* +!.claude/rules/ **/node_modules/** **/dist-nuitka/** **/.npmrc diff --git a/CLAUDE.md b/CLAUDE.md index 5d4b6cb96..fdc1b943b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,8 +24,8 @@ uv run mypy codeflash/ # Type check uv run ruff check codeflash/ # Lint uv run ruff format codeflash/ # Format -# Pre-commit (run before committing) -uv run pre-commit run --all-files +# Linting (run before committing) +uv run prek run --from-ref origin/main # Running the CLI uv run codeflash --help @@ -33,55 +33,6 @@ uv run codeflash init # Initialize in a project uv run codeflash --all # Optimize entire codebase ``` -## Architecture - -``` -codeflash/ -├── main.py # CLI entry point -├── cli_cmds/ # Command handling, console output (Rich) -├── discovery/ # Find optimizable functions -├── context/ # Extract code dependencies and imports -├── optimization/ # Generate optimized code via AI -│ ├── optimizer.py # Main optimization orchestration -│ └── function_optimizer.py # Per-function optimization logic -├── verification/ # Run deterministic tests (pytest plugin) -├── benchmarking/ # Performance measurement -├── github/ # PR creation -├── api/ # AI service communication -├── code_utils/ # Code parsing, git utilities -├── models/ # Pydantic models and types -├── tracing/ # Function call tracing -├── lsp/ # IDE integration (Language Server Protocol) -├── telemetry/ # Sentry, PostHog -├── either.py # Functional Result type for error handling -└── result/ # Result types and handling -``` - -### Key Rules to follow - -- Use libcst, not ast - For Python, always use `libcst` for code parsing/modification to preserve formatting. -- Code context extraction and replacement tests must always assert for full string equality, no substring matching. -- Any new feature or bug fix that can be tested automatically must have test cases. -- If changes affect existing test expectations, update the tests accordingly. Tests must always pass after changes. -- NEVER use leading underscores for function names (e.g., `_helper`). Python has no true private functions. Always use public names. - -## Code Style - -- **Line length**: 120 characters -- **Python**: 3.9+ syntax -- **Tooling**: Ruff for linting/formatting, mypy strict mode, pre-commit hooks -- **Comments**: Minimal - only explain "why", not "what" -- **Docstrings**: Do not add unless explicitly requested -- **Naming**: NEVER use leading underscores (`_function_name`) - Python has no true private functions, use public names -- **Paths**: Always use absolute paths, handle encoding explicitly (UTF-8) - -## Git Commits & Pull Requests - -- Use conventional commit format: `fix:`, `feat:`, `refactor:`, `docs:`, `test:`, `chore:` -- Keep commits atomic - one logical change per commit -- Commit message body should be concise (1-2 sentences max) -- PR titles should also use conventional format - # Agent Rules diff --git a/MULTI_LANGUAGE_ARCHITECTURE.md b/MULTI_LANGUAGE_ARCHITECTURE.md index e3cbaf4bb..5983afade 100644 --- a/MULTI_LANGUAGE_ARCHITECTURE.md +++ b/MULTI_LANGUAGE_ARCHITECTURE.md @@ -386,7 +386,7 @@ class JavaScriptTransformer: from pathlib import Path from codeflash.languages.base import LanguageSupport, FunctionInfo, CodeContext -from codeflash.languages.treesitter_utils import TreeSitterAnalyzer +from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer from codeflash.languages.javascript.transformer import JavaScriptTransformer class JavaScriptSupport(LanguageSupport): @@ -523,7 +523,7 @@ class JavaScriptSupport(LanguageSupport): # codeflash/languages/javascript/test_discovery.py from pathlib import Path -from codeflash.languages.treesitter_utils import TreeSitterAnalyzer +from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer class JestTestDiscovery: """Static analysis-based test discovery for Jest.""" diff --git a/code_to_optimize/js/code_to_optimize_vitest/package-lock.json b/code_to_optimize/js/code_to_optimize_vitest/package-lock.json index e08820803..ac3d39afd 100644 --- a/code_to_optimize/js/code_to_optimize_vitest/package-lock.json +++ b/code_to_optimize/js/code_to_optimize_vitest/package-lock.json @@ -15,7 +15,7 @@ } }, "../../../packages/codeflash": { - "version": "0.3.1", + "version": "0.7.0", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/codeflash/api/aiservice.py b/codeflash/api/aiservice.py index 4d1839455..cc59aadfb 100644 --- a/codeflash/api/aiservice.py +++ b/codeflash/api/aiservice.py @@ -841,6 +841,7 @@ def get_optimization_review( replay_tests: str, calling_fn_details: str, language: str = "python", + **_kwargs: Any, ) -> OptimizationReviewResult: """Compute the optimization review of current Pull Request. diff --git a/codeflash/api/cfapi.py b/codeflash/api/cfapi.py index 52e03c638..f7957fa0d 100644 --- a/codeflash/api/cfapi.py +++ b/codeflash/api/cfapi.py @@ -285,6 +285,7 @@ def create_staging( optimization_review: str = "", original_line_profiler: str | None = None, optimized_line_profiler: str | None = None, + **_kwargs: Any, # ignores the language argument TODO Hesham: staging for all langs ) -> Response: """Create a staging pull request, targeting the specified branch. (usually 'staging'). diff --git a/codeflash/cli_cmds/cli.py b/codeflash/cli_cmds/cli.py index 000eb5dd5..630311347 100644 --- a/codeflash/cli_cmds/cli.py +++ b/codeflash/cli_cmds/cli.py @@ -10,7 +10,7 @@ from codeflash.cli_cmds.console import logger from codeflash.cli_cmds.extension import install_vscode_extension from codeflash.code_utils import env_utils -from codeflash.code_utils.code_utils import exit_with_message +from codeflash.code_utils.code_utils import exit_with_message, normalize_ignore_paths from codeflash.code_utils.config_parser import parse_config_file from codeflash.languages.test_framework import set_current_test_framework from codeflash.lsp.helpers import is_LSP_enabled @@ -296,16 +296,12 @@ def process_pyproject_config(args: Namespace) -> Namespace: require_github_app_or_exit(owner, repo_name) - if hasattr(args, "ignore_paths") and args.ignore_paths is not None: - normalized_ignore_paths = [] - for path in args.ignore_paths: - path_obj = Path(path) - if path_obj.exists(): - normalized_ignore_paths.append(path_obj.resolve()) - # Silently skip non-existent paths (e.g., .next, dist before build) - args.ignore_paths = normalized_ignore_paths # Project root path is one level above the specified directory, because that's where the module can be imported from args.module_root = Path(args.module_root).resolve() + if hasattr(args, "ignore_paths") and args.ignore_paths is not None: + # Normalize ignore paths, supporting both literal paths and glob patterns + # Use module_root as base path for resolving relative paths and patterns + args.ignore_paths = normalize_ignore_paths(args.ignore_paths, base_path=args.module_root) # If module-root is "." then all imports are relatives to it. # in this case, the ".." becomes outside project scope, causing issues with un-importable paths args.project_root = project_root_from_module_root(args.module_root, pyproject_file_path) diff --git a/codeflash/cli_cmds/init_javascript.py b/codeflash/cli_cmds/init_javascript.py index 5ff61c010..9b3cd9fa9 100644 --- a/codeflash/cli_cmds/init_javascript.py +++ b/codeflash/cli_cmds/init_javascript.py @@ -154,6 +154,32 @@ def determine_js_package_manager(project_root: Path) -> JsPackageManager: return JsPackageManager.UNKNOWN +def find_node_modules_with_package(project_root: Path, package_name: str) -> Path | None: + """Find node_modules directory containing a specific package. + + Searches from project_root up to filesystem root for node_modules containing + the specified package. This supports monorepo setups where dependencies are + hoisted to the workspace root. + + Args: + project_root: Starting directory for the search. + package_name: Name of the package to look for (e.g., "jest", "vitest"). + + Returns: + Path to the node_modules directory containing the package, or None if not found. + + """ + current_dir = project_root.resolve() + while current_dir != current_dir.parent: + node_modules = current_dir / "node_modules" + if node_modules.exists(): + package_path = node_modules / package_name + if package_path.exists(): + return node_modules + current_dir = current_dir.parent + return None + + def get_package_install_command(project_root: Path, package: str, dev: bool = True) -> list[str]: """Get the correct install command for the project's package manager. diff --git a/codeflash/code_utils/code_extractor.py b/codeflash/code_utils/code_extractor.py index e2b9a9d52..c4434c3ae 100644 --- a/codeflash/code_utils/code_extractor.py +++ b/codeflash/code_utils/code_extractor.py @@ -1772,7 +1772,7 @@ def _extract_calling_function_js(source_code: str, function_name: str, ref_line: """ try: - from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage # Try TypeScript first, fall back to JavaScript for lang in [TreeSitterLanguage.TYPESCRIPT, TreeSitterLanguage.TSX, TreeSitterLanguage.JAVASCRIPT]: diff --git a/codeflash/code_utils/code_replacer.py b/codeflash/code_utils/code_replacer.py index e75d4e125..f4eb9cfc4 100644 --- a/codeflash/code_utils/code_replacer.py +++ b/codeflash/code_utils/code_replacer.py @@ -27,7 +27,7 @@ from codeflash.discovery.functions_to_optimize import FunctionToOptimize from codeflash.languages.base import Language, LanguageSupport - from codeflash.languages.treesitter_utils import TreeSitterAnalyzer + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer from codeflash.models.models import CodeOptimizationContext, CodeStringsMarkdown, OptimizedCandidate, ValidCode ASTNodeT = TypeVar("ASTNodeT", bound=ast.AST) @@ -765,7 +765,7 @@ def _add_global_declarations_for_language( return original_source try: - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(module_abspath) diff --git a/codeflash/code_utils/code_utils.py b/codeflash/code_utils/code_utils.py index 9244f6b11..95fc5d506 100644 --- a/codeflash/code_utils/code_utils.py +++ b/codeflash/code_utils/code_utils.py @@ -27,6 +27,57 @@ BLACKLIST_ADDOPTS = ("--benchmark", "--sugar", "--codespeed", "--cov", "--profile", "--junitxml", "-n") +# Characters that indicate a glob pattern +GLOB_PATTERN_CHARS = frozenset("*?[") + + +def is_glob_pattern(path_str: str) -> bool: + """Check if a path string contains glob pattern characters.""" + return any(char in path_str for char in GLOB_PATTERN_CHARS) + + +def normalize_ignore_paths(paths: list[str], base_path: Path | None = None) -> list[Path]: + """Normalize ignore paths, expanding glob patterns and resolving paths. + + Accepts a list of path strings that can be either: + - Literal paths (relative or absolute): e.g., "node_modules", "/absolute/path" + - Glob patterns: e.g., "**/*.test.js", "dist/*", "*.log" + + Args: + paths: List of path strings (literal paths or glob patterns). + base_path: Base path for resolving relative paths and patterns. + If None, uses current working directory. + + Returns: + List of resolved Path objects, deduplicated. + + """ + if base_path is None: + base_path = Path.cwd() + + base_path = base_path.resolve() + normalized: set[Path] = set() + + for path_str in paths: + if is_glob_pattern(path_str): + # It's a glob pattern - expand it + # Use base_path as the root for glob expansion + pattern_path = base_path / path_str + # glob returns an iterator of matching paths + for matched_path in base_path.glob(path_str): + if matched_path.exists(): + normalized.add(matched_path.resolve()) + else: + # It's a literal path + path_obj = Path(path_str) + if not path_obj.is_absolute(): + path_obj = base_path / path_obj + if path_obj.exists(): + normalized.add(path_obj.resolve()) + # Silently skip non-existent literal paths (e.g., .next, dist before build) + + return list(normalized) + def unified_diff_strings(code1: str, code2: str, fromfile: str = "original", tofile: str = "modified") -> str: """Return the unified diff between two code strings as a single string. diff --git a/codeflash/code_utils/edit_generated_tests.py b/codeflash/code_utils/edit_generated_tests.py index 3d782b32d..7ec303b7a 100644 --- a/codeflash/code_utils/edit_generated_tests.py +++ b/codeflash/code_utils/edit_generated_tests.py @@ -361,21 +361,27 @@ def normalize_codeflash_imports(source: str) -> str: return _CODEFLASH_IMPORT_PATTERN.sub(r"import \1 from 'codeflash'", source) -def inject_test_globals(generated_tests: GeneratedTestsList) -> GeneratedTestsList: +def inject_test_globals(generated_tests: GeneratedTestsList, test_framework: str = "jest") -> GeneratedTestsList: # TODO: inside the prompt tell the llm if it should import jest functions or it's already injected in the global window """Inject test globals into all generated tests. Args: generated_tests: List of generated tests. + test_framework: The test framework being used ("jest", "vitest", or "mocha"). Returns: Generated tests with test globals injected. """ # we only inject test globals for esm modules - global_import = ( - "import { jest, describe, it, expect, beforeEach, afterEach, beforeAll, test } from '@jest/globals'\n" - ) + # Use vitest imports for vitest projects, jest imports for jest projects + if test_framework == "vitest": + global_import = "import { vi, describe, it, expect, beforeEach, afterEach, beforeAll, test } from 'vitest'\n" + else: + # Default to jest imports for jest and other frameworks + global_import = ( + "import { jest, describe, it, expect, beforeEach, afterEach, beforeAll, test } from '@jest/globals'\n" + ) for test in generated_tests.generated_tests: test.generated_original_test_source = global_import + test.generated_original_test_source diff --git a/codeflash/code_utils/normalizers/javascript.py b/codeflash/code_utils/normalizers/javascript.py index e3a4faae0..fa61b19a7 100644 --- a/codeflash/code_utils/normalizers/javascript.py +++ b/codeflash/code_utils/normalizers/javascript.py @@ -233,7 +233,7 @@ def normalize(self, code: str) -> str: """ try: - from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage lang_map = {"javascript": TreeSitterLanguage.JAVASCRIPT, "typescript": TreeSitterLanguage.TYPESCRIPT} lang = lang_map.get(self._get_tree_sitter_language(), TreeSitterLanguage.JAVASCRIPT) diff --git a/codeflash/discovery/functions_to_optimize.py b/codeflash/discovery/functions_to_optimize.py index 740528c0c..29bea8761 100644 --- a/codeflash/discovery/functions_to_optimize.py +++ b/codeflash/discovery/functions_to_optimize.py @@ -201,7 +201,7 @@ def _is_js_ts_function_exported(file_path: Path, function_name: str) -> tuple[bo Tuple of (is_exported, export_name). export_name may be 'default' for default exports. """ - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file try: source = file_path.read_text(encoding="utf-8") diff --git a/codeflash/languages/javascript/find_references.py b/codeflash/languages/javascript/find_references.py index 87cf63bb9..ed6e30636 100644 --- a/codeflash/languages/javascript/find_references.py +++ b/codeflash/languages/javascript/find_references.py @@ -23,7 +23,7 @@ from tree_sitter import Node from codeflash.discovery.functions_to_optimize import FunctionToOptimize - from codeflash.languages.treesitter_utils import ImportInfo, TreeSitterAnalyzer + from codeflash.languages.javascript.treesitter import ImportInfo, TreeSitterAnalyzer logger = logging.getLogger(__name__) @@ -112,7 +112,7 @@ def find_references( List of Reference objects describing each call site. """ - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file function_name = function_to_optimize.function_name source_file = function_to_optimize.file_path @@ -719,7 +719,7 @@ def _find_reexports_direct( continue # Create a fake ImportInfo to resolve the re-export source - from codeflash.languages.treesitter_utils import ImportInfo + from codeflash.languages.javascript.treesitter import ImportInfo fake_import = ImportInfo( module_path=exp.reexport_source, diff --git a/codeflash/languages/javascript/import_resolver.py b/codeflash/languages/javascript/import_resolver.py index 4e237b8d6..8f5dbe8ca 100644 --- a/codeflash/languages/javascript/import_resolver.py +++ b/codeflash/languages/javascript/import_resolver.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from codeflash.discovery.functions_to_optimize import FunctionToOptimize from codeflash.languages.base import HelperFunction - from codeflash.languages.treesitter_utils import ImportInfo, TreeSitterAnalyzer + from codeflash.languages.javascript.treesitter import ImportInfo, TreeSitterAnalyzer logger = logging.getLogger(__name__) @@ -486,7 +486,7 @@ def _extract_helper_from_file( """ from codeflash.languages.base import HelperFunction - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file try: source = file_path.read_text(encoding="utf-8") @@ -558,7 +558,8 @@ def _find_helpers_recursive( """ from codeflash.discovery.functions_to_optimize import FunctionToOptimize - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file + from codeflash.languages.registry import get_language_support if context.current_depth >= context.max_depth: return {} @@ -578,12 +579,15 @@ def _find_helpers_recursive( imports = analyzer.find_imports(source) # Create FunctionToOptimize for the helper + # Get language from the language support registry + lang_support = get_language_support(file_path) func_info = FunctionToOptimize( function_name=helper.name, file_path=file_path, parents=[], starting_line=helper.start_line, ending_line=helper.end_line, + language=str(lang_support.language), ) # Recursively find helpers diff --git a/codeflash/languages/javascript/instrument.py b/codeflash/languages/javascript/instrument.py index ebcefb8c4..551d99f0f 100644 --- a/codeflash/languages/javascript/instrument.py +++ b/codeflash/languages/javascript/instrument.py @@ -788,7 +788,7 @@ def validate_and_fix_import_style(test_code: str, source_file_path: Path, functi Fixed test code with correct import style. """ - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file # Read source file to determine export style try: @@ -897,6 +897,115 @@ def is_relevant_import(module_path: str) -> bool: return test_code +def fix_import_path_for_test_location( + test_code: str, source_file_path: Path, test_file_path: Path, module_root: Path +) -> str: + """Fix import paths in generated test code to be relative to test file location. + + The AI may generate tests with import paths that are relative to the module root + (e.g., 'apps/web/app/file') instead of relative to where the test file is located + (e.g., '../../app/file'). This function fixes such imports. + + Args: + test_code: The generated test code. + source_file_path: Absolute path to the source file being tested. + test_file_path: Absolute path to where the test file will be written. + module_root: Root directory of the module/project. + + Returns: + Test code with corrected import paths. + + """ + import os + + # Calculate the correct relative import path from test file to source file + test_dir = test_file_path.parent + try: + correct_rel_path = os.path.relpath(source_file_path, test_dir) + correct_rel_path = correct_rel_path.replace("\\", "/") + # Remove file extension for JS/TS imports + for ext in [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"]: + if correct_rel_path.endswith(ext): + correct_rel_path = correct_rel_path[: -len(ext)] + break + # Ensure it starts with ./ or ../ + if not correct_rel_path.startswith("."): + correct_rel_path = "./" + correct_rel_path + except ValueError: + # Can't compute relative path (different drives on Windows) + return test_code + + # Try to compute what incorrect path the AI might have generated + # The AI often uses module_root-relative paths like 'apps/web/app/...' + try: + source_rel_to_module = os.path.relpath(source_file_path, module_root) + source_rel_to_module = source_rel_to_module.replace("\\", "/") + # Remove extension + for ext in [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"]: + if source_rel_to_module.endswith(ext): + source_rel_to_module = source_rel_to_module[: -len(ext)] + break + except ValueError: + return test_code + + # Also check for project root-relative paths (including module_root in path) + try: + project_root = module_root.parent if module_root.name in ["src", "lib", "app", "web", "apps"] else module_root + source_rel_to_project = os.path.relpath(source_file_path, project_root) + source_rel_to_project = source_rel_to_project.replace("\\", "/") + for ext in [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"]: + if source_rel_to_project.endswith(ext): + source_rel_to_project = source_rel_to_project[: -len(ext)] + break + except ValueError: + source_rel_to_project = None + + # Source file name (for matching module paths that end with the file name) + source_name = source_file_path.stem + + # Patterns to find import statements + # ESM: import { func } from 'path' or import func from 'path' + esm_import_pattern = re.compile(r"(import\s+(?:{[^}]+}|\w+)\s+from\s+['\"])([^'\"]+)(['\"])") + # CommonJS: const { func } = require('path') or const func = require('path') + cjs_require_pattern = re.compile( + r"((?:const|let|var)\s+(?:{[^}]+}|\w+)\s*=\s*require\s*\(\s*['\"])([^'\"]+)(['\"])" + ) + + def should_fix_path(import_path: str) -> bool: + """Check if this import path looks like it should point to our source file.""" + # Skip relative imports that already look correct + if import_path.startswith(("./", "../")): + return False + # Skip package imports (no path separators or start with @) + if "/" not in import_path and "\\" not in import_path: + return False + if import_path.startswith("@") and "/" in import_path: + # Could be an alias like @/utils - skip these + return False + # Check if it looks like it points to our source file + if import_path == source_rel_to_module: + return True + if source_rel_to_project and import_path == source_rel_to_project: + return True + if import_path.endswith((source_name, "/" + source_name)): + return True + return False + + def fix_import(match: re.Match[str]) -> str: + """Replace incorrect import path with correct relative path.""" + prefix = match.group(1) + import_path = match.group(2) + suffix = match.group(3) + + if should_fix_path(import_path): + logger.debug(f"Fixing import path: {import_path} -> {correct_rel_path}") + return f"{prefix}{correct_rel_path}{suffix}" + return match.group(0) + + test_code = esm_import_pattern.sub(fix_import, test_code) + return cjs_require_pattern.sub(fix_import, test_code) + + def get_instrumented_test_path(original_path: Path, mode: str) -> Path: """Generate path for instrumented test file. diff --git a/codeflash/languages/javascript/line_profiler.py b/codeflash/languages/javascript/line_profiler.py index 57f046d4a..81b38983c 100644 --- a/codeflash/languages/javascript/line_profiler.py +++ b/codeflash/languages/javascript/line_profiler.py @@ -11,7 +11,7 @@ import logging from typing import TYPE_CHECKING -from codeflash.languages.treesitter_utils import get_analyzer_for_file +from codeflash.languages.javascript.treesitter import get_analyzer_for_file if TYPE_CHECKING: from pathlib import Path diff --git a/codeflash/languages/javascript/module_system.py b/codeflash/languages/javascript/module_system.py index 3e3ff29dc..66e6fe7e3 100644 --- a/codeflash/languages/javascript/module_system.py +++ b/codeflash/languages/javascript/module_system.py @@ -416,8 +416,10 @@ def ensure_module_system_compatibility(code: str, target_module_system: str, pro is_esm = has_import or has_export # Convert if needed - if target_module_system == ModuleSystem.ES_MODULE and is_commonjs and not is_esm: - logger.debug("Converting CommonJS to ES Module syntax") + # For ESM target: convert any require statements, even if there are also import statements + # This handles generated tests that have ESM imports for test globals but CommonJS for the function + if target_module_system == ModuleSystem.ES_MODULE and has_require: + logger.debug("Converting CommonJS require statements to ES Module syntax") return convert_commonjs_to_esm(code) if target_module_system == ModuleSystem.COMMONJS and is_esm and not is_commonjs: diff --git a/codeflash/languages/javascript/parse.py b/codeflash/languages/javascript/parse.py new file mode 100644 index 000000000..1bfda8bca --- /dev/null +++ b/codeflash/languages/javascript/parse.py @@ -0,0 +1,530 @@ +"""Jest/Vitest JUnit XML parsing for JavaScript/TypeScript tests. + +This module handles parsing of JUnit XML test results produced by Jest and Vitest +test runners. It extracts test results, timing information, and maps them back +to instrumented test files. +""" + +from __future__ import annotations + +import contextlib +import json +import re +from pathlib import Path +from typing import TYPE_CHECKING + +from junitparser.xunit2 import JUnitXml + +from codeflash.cli_cmds.console import logger +from codeflash.models.models import FunctionTestInvocation, InvocationId, TestResults, TestType + +if TYPE_CHECKING: + import subprocess + + from codeflash.models.models import TestFiles + from codeflash.verification.verification_utils import TestConfig + + +# Jest timing marker patterns (from codeflash-jest-helper.js console.log output) +# Format: !$######testName:testName:funcName:loopIndex:lineId######$! (start) +# Format: !######testName:testName:funcName:loopIndex:lineId:durationNs######! (end) +jest_start_pattern = re.compile(r"!\$######([^:]+):([^:]+):([^:]+):([^:]+):([^#]+)######\$!") +jest_end_pattern = re.compile(r"!######([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):(\d+)######!") + + +def _extract_jest_console_output(suite_elem) -> str: + """Extract console output from Jest's JUnit XML system-out element. + + Jest-junit writes console.log output as a JSON array in the testsuite's system-out. + Each entry has: {"message": "...", "origin": "...", "type": "log"} + + Args: + suite_elem: The testsuite lxml element + + Returns: + Concatenated message content from all log entries + + """ + system_out_elem = suite_elem.find("system-out") + if system_out_elem is None or system_out_elem.text is None: + return "" + + raw_content = system_out_elem.text.strip() + if not raw_content: + return "" + + # Jest-junit wraps console output in a JSON array + # Try to parse as JSON first + try: + log_entries = json.loads(raw_content) + if isinstance(log_entries, list): + # Extract message field from each log entry + messages = [] + for entry in log_entries: + if isinstance(entry, dict) and "message" in entry: + messages.append(entry["message"]) + return "\n".join(messages) + except (json.JSONDecodeError, TypeError): + # Not JSON - return as plain text (fallback for pytest-style output) + pass + + return raw_content + + +def parse_jest_test_xml( + test_xml_file_path: Path, + test_files: TestFiles, + test_config: TestConfig, + run_result: subprocess.CompletedProcess | None = None, + parse_func=None, + resolve_test_file_from_class_path=None, +) -> TestResults: + """Parse Jest JUnit XML test results. + + Jest-junit has a different structure than pytest: + - system-out is at the testsuite level (not testcase) + - system-out contains a JSON array of log entries + - Timing markers are in the message field of log entries + + Args: + test_xml_file_path: Path to the Jest JUnit XML file + test_files: TestFiles object with test file information + test_config: Test configuration + run_result: Optional subprocess result for logging + parse_func: XML parser function (injected to avoid circular imports) + resolve_test_file_from_class_path: Function to resolve test file paths (injected) + + Returns: + TestResults containing parsed test invocations + + """ + test_results = TestResults() + + if not test_xml_file_path.exists(): + logger.warning(f"No JavaScript test results for {test_xml_file_path} found.") + return test_results + + # Log file size for debugging + file_size = test_xml_file_path.stat().st_size + logger.debug(f"Jest XML file size: {file_size} bytes at {test_xml_file_path}") + + try: + xml = JUnitXml.fromfile(str(test_xml_file_path), parse_func=parse_func) + logger.debug(f"Successfully parsed Jest JUnit XML from {test_xml_file_path}") + except Exception as e: + logger.warning(f"Failed to parse {test_xml_file_path} as JUnitXml. Exception: {e}") + return test_results + + base_dir = test_config.tests_project_rootdir + logger.debug(f"Jest XML parsing: base_dir={base_dir}, num_test_files={len(test_files.test_files)}") + + # Build lookup from instrumented file path to TestFile for direct matching + # This handles cases where instrumented files are in temp directories + instrumented_path_lookup: dict[str, tuple[Path, TestType]] = {} + for test_file in test_files.test_files: + # Add behavior instrumented file paths + if test_file.instrumented_behavior_file_path: + # Store both the absolute path and resolved path as keys + abs_path = str(test_file.instrumented_behavior_file_path.resolve()) + instrumented_path_lookup[abs_path] = (test_file.instrumented_behavior_file_path, test_file.test_type) + # Also store the string representation in case of minor path differences + instrumented_path_lookup[str(test_file.instrumented_behavior_file_path)] = ( + test_file.instrumented_behavior_file_path, + test_file.test_type, + ) + logger.debug(f"Jest XML lookup: registered {abs_path}") + # Also add benchmarking file paths (perf-only instrumented tests) + if test_file.benchmarking_file_path: + bench_abs_path = str(test_file.benchmarking_file_path.resolve()) + instrumented_path_lookup[bench_abs_path] = (test_file.benchmarking_file_path, test_file.test_type) + instrumented_path_lookup[str(test_file.benchmarking_file_path)] = ( + test_file.benchmarking_file_path, + test_file.test_type, + ) + logger.debug(f"Jest XML lookup: registered benchmark {bench_abs_path}") + + # Also build a filename-only lookup for fallback matching + # This handles cases where JUnit XML has relative paths that don't match absolute paths + # e.g., JUnit has "test/utils__perfinstrumented.test.ts" but lookup has absolute paths + filename_lookup: dict[str, tuple[Path, TestType]] = {} + for test_file in test_files.test_files: + # Add instrumented_behavior_file_path (behavior tests) + if test_file.instrumented_behavior_file_path: + filename = test_file.instrumented_behavior_file_path.name + # Only add if not already present (avoid overwrites in case of duplicate filenames) + if filename not in filename_lookup: + filename_lookup[filename] = (test_file.instrumented_behavior_file_path, test_file.test_type) + logger.debug(f"Jest XML filename lookup: registered {filename}") + # Also add benchmarking_file_path (perf-only tests) - these have different filenames + # e.g., utils__perfonlyinstrumented.test.ts vs utils__perfinstrumented.test.ts + if test_file.benchmarking_file_path: + bench_filename = test_file.benchmarking_file_path.name + if bench_filename not in filename_lookup: + filename_lookup[bench_filename] = (test_file.benchmarking_file_path, test_file.test_type) + logger.debug(f"Jest XML filename lookup: registered benchmark file {bench_filename}") + + # Fallback: if JUnit XML doesn't have system-out, use subprocess stdout directly + global_stdout = "" + if run_result is not None: + try: + global_stdout = run_result.stdout if isinstance(run_result.stdout, str) else run_result.stdout.decode() + # Debug: log if timing markers are found in stdout + if global_stdout: + marker_count = len(jest_start_pattern.findall(global_stdout)) + if marker_count > 0: + logger.debug(f"Found {marker_count} timing start markers in Jest stdout") + else: + logger.debug(f"No timing start markers found in Jest stdout (len={len(global_stdout)})") + # Check for END markers with duration (perf test markers) + end_marker_count = len(jest_end_pattern.findall(global_stdout)) + if end_marker_count > 0: + logger.debug( + f"[PERF-DEBUG] Found {end_marker_count} END timing markers with duration in Jest stdout" + ) + # Sample a few markers to verify loop indices + end_samples = list(jest_end_pattern.finditer(global_stdout))[:5] + for sample in end_samples: + groups = sample.groups() + logger.debug(f"[PERF-DEBUG] Sample END marker: loopIndex={groups[3]}, duration={groups[5]}") + else: + logger.debug("[PERF-DEBUG] No END markers with duration found in Jest stdout") + except (AttributeError, UnicodeDecodeError): + global_stdout = "" + + suite_count = 0 + testcase_count = 0 + for suite in xml: + suite_count += 1 + # Extract console output from suite-level system-out (Jest specific) + suite_stdout = _extract_jest_console_output(suite._elem) # noqa: SLF001 + + # Fallback: use subprocess stdout if XML system-out is empty + if not suite_stdout and global_stdout: + suite_stdout = global_stdout + + # Parse timing markers from the suite's console output + start_matches = list(jest_start_pattern.finditer(suite_stdout)) + end_matches_dict = {} + for match in jest_end_pattern.finditer(suite_stdout): + # Key: (testName, testName2, funcName, loopIndex, lineId) + key = match.groups()[:5] + end_matches_dict[key] = match + + # Debug: log suite-level END marker parsing for perf tests + if end_matches_dict: + # Get unique loop indices from the parsed END markers + loop_indices = sorted({int(k[3]) if k[3].isdigit() else 1 for k in end_matches_dict}) + logger.debug( + f"[PERF-DEBUG] Suite {suite_count}: parsed {len(end_matches_dict)} END markers from suite_stdout, loop_index range: {min(loop_indices)}-{max(loop_indices)}" + ) + + # Also collect timing markers from testcase-level system-out (Vitest puts output at testcase level) + for tc in suite: + tc_system_out = tc._elem.find("system-out") # noqa: SLF001 + if tc_system_out is not None and tc_system_out.text: + tc_stdout = tc_system_out.text.strip() + logger.debug(f"Vitest testcase system-out found: {len(tc_stdout)} chars, first 200: {tc_stdout[:200]}") + end_marker_count = 0 + for match in jest_end_pattern.finditer(tc_stdout): + key = match.groups()[:5] + end_matches_dict[key] = match + end_marker_count += 1 + if end_marker_count > 0: + logger.debug(f"Found {end_marker_count} END timing markers in testcase system-out") + start_matches.extend(jest_start_pattern.finditer(tc_stdout)) + + for testcase in suite: + testcase_count += 1 + test_class_path = testcase.classname # For Jest, this is the file path + test_name = testcase.name + + if test_name is None: + logger.debug(f"testcase.name is None in Jest XML {test_xml_file_path}, skipping") + continue + + logger.debug(f"Jest XML: processing testcase name={test_name}, classname={test_class_path}") + + # First, try direct lookup in instrumented file paths + # This handles cases where instrumented files are in temp directories + test_file_path = None + test_type = None + + if test_class_path: + # Try exact match with classname (which should be the filepath from jest-junit) + if test_class_path in instrumented_path_lookup: + test_file_path, test_type = instrumented_path_lookup[test_class_path] + else: + # Try resolving the path and matching + try: + resolved_path = str(Path(test_class_path).resolve()) + if resolved_path in instrumented_path_lookup: + test_file_path, test_type = instrumented_path_lookup[resolved_path] + except Exception: + pass + + # If direct lookup failed, try the file attribute + if test_file_path is None: + test_file_name = suite._elem.attrib.get("file") or testcase._elem.attrib.get("file") # noqa: SLF001 + if test_file_name: + if test_file_name in instrumented_path_lookup: + test_file_path, test_type = instrumented_path_lookup[test_file_name] + else: + try: + resolved_path = str(Path(test_file_name).resolve()) + if resolved_path in instrumented_path_lookup: + test_file_path, test_type = instrumented_path_lookup[resolved_path] + except Exception: + pass + + # Fall back to traditional path resolution if direct lookup failed + if test_file_path is None and resolve_test_file_from_class_path is not None: + test_file_path = resolve_test_file_from_class_path(test_class_path, base_dir) + if test_file_path is None: + test_file_name = suite._elem.attrib.get("file") or testcase._elem.attrib.get("file") # noqa: SLF001 + if test_file_name: + test_file_path = base_dir.parent / test_file_name + if not test_file_path.exists(): + test_file_path = base_dir / test_file_name + + # Fallback: try matching by filename only + # This handles when JUnit XML has relative paths like "test/utils__perfinstrumented.test.ts" + # that can't be resolved to absolute paths because they're relative to Jest's CWD, not parse CWD + if test_file_path is None and test_class_path: + # Extract filename from the path (handles both forward and back slashes) + path_filename = Path(test_class_path).name + if path_filename in filename_lookup: + test_file_path, test_type = filename_lookup[path_filename] + logger.debug(f"Jest XML: matched by filename {path_filename}") + + # Also try filename matching on the file attribute if classname matching failed + if test_file_path is None: + test_file_name = suite._elem.attrib.get("file") or testcase._elem.attrib.get("file") # noqa: SLF001 + if test_file_name: + file_attr_filename = Path(test_file_name).name + if file_attr_filename in filename_lookup: + test_file_path, test_type = filename_lookup[file_attr_filename] + logger.debug(f"Jest XML: matched by file attr filename {file_attr_filename}") + + # For Jest tests in monorepos, test files may not exist after cleanup + # but we can still parse results and infer test type from the path + if test_file_path is None: + logger.warning(f"Could not resolve test file for Jest test: {test_class_path}") + continue + + # Get test type if not already set from lookup + if test_type is None and test_file_path.exists(): + test_type = test_files.get_test_type_by_instrumented_file_path(test_file_path) + if test_type is None: + # Infer test type from filename pattern + filename = test_file_path.name + if "__perf_test_" in filename or "_perf_test_" in filename: + test_type = TestType.GENERATED_PERFORMANCE + elif "__unit_test_" in filename or "_unit_test_" in filename: + test_type = TestType.GENERATED_REGRESSION + else: + # Default to GENERATED_REGRESSION for Jest tests + test_type = TestType.GENERATED_REGRESSION + + # For Jest tests, keep the relative file path with extension intact + # (Python uses module_name_from_file_path which strips extensions) + try: + test_module_path = str(test_file_path.relative_to(test_config.tests_project_rootdir)) + except ValueError: + test_module_path = test_file_path.name + result = testcase.is_passed + + # Check for timeout + timed_out = False + if len(testcase.result) >= 1: + message = (testcase.result[0].message or "").lower() + if "timeout" in message or "timed out" in message: + timed_out = True + + # Find matching timing markers for this test + # Jest test names in markers are sanitized by codeflash-jest-helper's sanitizeTestId() + # which replaces: !#: (space) ()[]{}|\/*?^$.+- with underscores + # IMPORTANT: Must match Jest helper's sanitization exactly for marker matching to work + # Pattern from capture.js: /[!#: ()\[\]{}|\\/*?^$.+\-]/g + sanitized_test_name = re.sub(r"[!#: ()\[\]{}|\\/*?^$.+\-]", "_", test_name) + matching_starts = [m for m in start_matches if sanitized_test_name in m.group(2)] + + # Debug: log which branch we're taking + logger.debug( + f"[FLOW-DEBUG] Testcase '{test_name[:50]}': " + f"total_start_matches={len(start_matches)}, matching_starts={len(matching_starts)}, " + f"total_end_matches={len(end_matches_dict)}" + ) + + # For performance tests (capturePerf), there are no START markers - only END markers with duration + # Check for END markers directly if no START markers found + matching_ends_direct = [] + if not matching_starts: + # Look for END markers that match this test (performance test format) + # END marker format: !######module:testName:funcName:loopIndex:invocationId:durationNs######! + for end_key, end_match in end_matches_dict.items(): + # end_key is (module, testName, funcName, loopIndex, invocationId) + if len(end_key) >= 2 and sanitized_test_name in end_key[1]: + matching_ends_direct.append(end_match) + # Debug: log matching results for perf tests + if matching_ends_direct: + loop_indices = [int(m.groups()[3]) if m.groups()[3].isdigit() else 1 for m in matching_ends_direct] + logger.debug( + f"[PERF-MATCH] Testcase '{test_name[:40]}': matched {len(matching_ends_direct)} END markers, " + f"loop_index range: {min(loop_indices)}-{max(loop_indices)}" + ) + elif end_matches_dict: + # No matches but we have END markers - check why + sample_keys = list(end_matches_dict.keys())[:3] + logger.debug( + f"[PERF-MISMATCH] Testcase '{test_name[:40]}': no matches found. " + f"sanitized_test_name='{sanitized_test_name[:50]}', " + f"sample end_keys={[k[1][:30] if len(k) >= 2 else k for k in sample_keys]}" + ) + + # Log if we're skipping the matching_ends_direct branch + if matching_starts and end_matches_dict: + logger.debug( + f"[FLOW-SKIP] Testcase '{test_name[:40]}': has {len(matching_starts)} START markers, " + f"skipping {len(end_matches_dict)} END markers (behavior test mode)" + ) + + if not matching_starts and not matching_ends_direct: + # No timing markers found - use JUnit XML time attribute as fallback + # The time attribute is in seconds (e.g., "0.00077875"), convert to nanoseconds + runtime = None + try: + time_attr = testcase._elem.attrib.get("time") # noqa: SLF001 + if time_attr: + time_seconds = float(time_attr) + runtime = int(time_seconds * 1_000_000_000) # Convert seconds to nanoseconds + logger.debug(f"Jest XML: using time attribute for {test_name}: {time_seconds}s = {runtime}ns") + except (ValueError, TypeError) as e: + logger.debug(f"Jest XML: could not parse time attribute: {e}") + + test_results.add( + FunctionTestInvocation( + loop_index=1, + id=InvocationId( + test_module_path=test_module_path, + test_class_name=None, + test_function_name=test_name, + function_getting_tested="", + iteration_id="", + ), + file_name=test_file_path, + runtime=runtime, + test_framework=test_config.test_framework, + did_pass=result, + test_type=test_type, + return_value=None, + timed_out=timed_out, + stdout="", + ) + ) + elif matching_ends_direct: + # Performance test format: process END markers directly (no START markers) + loop_indices_found = [] + for end_match in matching_ends_direct: + groups = end_match.groups() + # groups: (module, testName, funcName, loopIndex, invocationId, durationNs) + func_name = groups[2] + loop_index = int(groups[3]) if groups[3].isdigit() else 1 + loop_indices_found.append(loop_index) + line_id = groups[4] + try: + runtime = int(groups[5]) + except (ValueError, IndexError): + runtime = None + test_results.add( + FunctionTestInvocation( + loop_index=loop_index, + id=InvocationId( + test_module_path=test_module_path, + test_class_name=None, + test_function_name=test_name, + function_getting_tested=func_name, + iteration_id=line_id, + ), + file_name=test_file_path, + runtime=runtime, + test_framework=test_config.test_framework, + did_pass=result, + test_type=test_type, + return_value=None, + timed_out=timed_out, + stdout="", + ) + ) + if loop_indices_found: + logger.debug( + f"[LOOP-DEBUG] Testcase '{test_name}': processed {len(matching_ends_direct)} END markers, " + f"loop_index range: {min(loop_indices_found)}-{max(loop_indices_found)}, " + f"total results so far: {len(test_results.test_results)}" + ) + else: + # Process each timing marker + for match in matching_starts: + groups = match.groups() + # groups: (testName, testName2, funcName, loopIndex, lineId) + func_name = groups[2] + loop_index = int(groups[3]) if groups[3].isdigit() else 1 + line_id = groups[4] + + # Find matching end marker + end_key = groups[:5] + end_match = end_matches_dict.get(end_key) + + runtime = None + if end_match: + # Duration is in the 6th group (index 5) + with contextlib.suppress(ValueError, IndexError): + runtime = int(end_match.group(6)) + test_results.add( + FunctionTestInvocation( + loop_index=loop_index, + id=InvocationId( + test_module_path=test_module_path, + test_class_name=None, + test_function_name=test_name, + function_getting_tested=func_name, + iteration_id=line_id, + ), + file_name=test_file_path, + runtime=runtime, + test_framework=test_config.test_framework, + did_pass=result, + test_type=test_type, + return_value=None, + timed_out=timed_out, + stdout="", + ) + ) + + if not test_results: + logger.info( + f"No Jest test results parsed from {test_xml_file_path} " + f"(found {suite_count} suites, {testcase_count} testcases)" + ) + if run_result is not None: + logger.debug(f"Jest stdout: {run_result.stdout[:1000] if run_result.stdout else 'empty'}") + else: + logger.debug( + f"Jest XML parsing complete: {len(test_results.test_results)} results " + f"from {suite_count} suites, {testcase_count} testcases" + ) + # Debug: show loop_index distribution for perf analysis + if test_results.test_results: + loop_indices = [r.loop_index for r in test_results.test_results] + unique_loop_indices = sorted(set(loop_indices)) + min_idx, max_idx = min(unique_loop_indices), max(unique_loop_indices) + logger.debug( + f"[LOOP-SUMMARY] Results loop_index: min={min_idx}, max={max_idx}, " + f"unique_count={len(unique_loop_indices)}, total_results={len(loop_indices)}" + ) + if max_idx == 1 and len(loop_indices) > 1: + logger.warning( + f"[LOOP-WARNING] All {len(loop_indices)} results have loop_index=1. " + "Perf test markers may not have been parsed correctly." + ) + + return test_results diff --git a/codeflash/languages/javascript/support.py b/codeflash/languages/javascript/support.py index 149e2bcd7..6a67821a8 100644 --- a/codeflash/languages/javascript/support.py +++ b/codeflash/languages/javascript/support.py @@ -14,15 +14,15 @@ from codeflash.discovery.functions_to_optimize import FunctionToOptimize from codeflash.languages.base import CodeContext, FunctionFilterCriteria, HelperFunction, Language, TestInfo, TestResult +from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage, get_analyzer_for_file from codeflash.languages.registry import register_language -from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage, get_analyzer_for_file from codeflash.models.models import FunctionParent if TYPE_CHECKING: from collections.abc import Sequence from codeflash.languages.base import ReferenceInfo - from codeflash.languages.treesitter_utils import TypeDefinition + from codeflash.languages.javascript.treesitter import TypeDefinition logger = logging.getLogger(__name__) @@ -1846,8 +1846,11 @@ def verify_requirements(self, project_root: Path, test_framework: str = "jest") Checks for: 1. Node.js installation 2. npm availability - 3. Test framework (jest/vitest) installation - 4. node_modules existence + 3. Test framework (jest/vitest) installation (with monorepo support) + + Uses find_node_modules_with_package() from init_javascript to search up the + directory tree for node_modules containing the test framework. This supports + monorepo setups where dependencies are hoisted to the workspace root. Args: project_root: The project root directory. @@ -1879,16 +1882,21 @@ def verify_requirements(self, project_root: Path, test_framework: str = "jest") except Exception as e: errors.append(f"Failed to check npm: {e}") - # Check node_modules exists - node_modules = project_root / "node_modules" - if not node_modules.exists(): - errors.append( - f"node_modules not found in {project_root}. Please run 'npm install' to install dependencies." - ) + # Check test framework is installed (with monorepo support) + # Uses find_node_modules_with_package which searches up the directory tree + from codeflash.cli_cmds.init_javascript import find_node_modules_with_package + + node_modules = find_node_modules_with_package(project_root, test_framework) + if node_modules: + logger.debug("Found %s in node_modules at %s", test_framework, node_modules / test_framework) else: - # Check test framework is installed - framework_path = node_modules / test_framework - if not framework_path.exists(): + # Check if local node_modules exists at all + local_node_modules = project_root / "node_modules" + if not local_node_modules.exists(): + errors.append( + f"node_modules not found in {project_root}. Please run 'npm install' to install dependencies." + ) + else: errors.append( f"{test_framework} is not installed. " f"Please run 'npm install --save-dev {test_framework}' to install it." @@ -2092,6 +2100,10 @@ def run_behavioral_tests( candidate_index=candidate_index, ) + # JavaScript/TypeScript benchmarking uses high max_loops like Python (100,000) + # The actual loop count is limited by target_duration_seconds, not max_loops + JS_BENCHMARKING_MAX_LOOPS = 100_000 + def run_benchmarking_tests( self, test_paths: Any, @@ -2124,10 +2136,15 @@ def run_benchmarking_tests( from codeflash.languages.test_framework import get_js_test_framework_or_default framework = test_framework or get_js_test_framework_or_default() + logger.debug("run_benchmarking_tests called with framework=%s", framework) + + # Use JS-specific high max_loops - actual loop count is limited by target_duration + effective_max_loops = self.JS_BENCHMARKING_MAX_LOOPS if framework == "vitest": from codeflash.languages.javascript.vitest_runner import run_vitest_benchmarking_tests + logger.debug("Dispatching to run_vitest_benchmarking_tests") return run_vitest_benchmarking_tests( test_paths=test_paths, test_env=test_env, @@ -2135,7 +2152,7 @@ def run_benchmarking_tests( timeout=timeout, project_root=project_root, min_loops=min_loops, - max_loops=max_loops, + max_loops=effective_max_loops, target_duration_ms=int(target_duration_seconds * 1000), ) @@ -2148,7 +2165,7 @@ def run_benchmarking_tests( timeout=timeout, project_root=project_root, min_loops=min_loops, - max_loops=max_loops, + max_loops=effective_max_loops, target_duration_ms=int(target_duration_seconds * 1000), ) diff --git a/codeflash/languages/treesitter_utils.py b/codeflash/languages/javascript/treesitter.py similarity index 96% rename from codeflash/languages/treesitter_utils.py rename to codeflash/languages/javascript/treesitter.py index a3aa2ccb5..650d899a5 100644 --- a/codeflash/languages/treesitter_utils.py +++ b/codeflash/languages/javascript/treesitter.py @@ -94,6 +94,9 @@ class ExportInfo: reexport_source: str | None # Module path for re-exports start_line: int end_line: int + # Functions passed as arguments to wrapper calls in default exports + # e.g., export default curry(traverseEntity) -> ["traverseEntity"] + wrapped_default_args: list[str] | None = None @dataclass @@ -707,6 +710,7 @@ def _extract_export_info(self, node: Node, source_bytes: bytes) -> ExportInfo | default_export: str | None = None is_reexport = False reexport_source: str | None = None + wrapped_default_args: list[str] | None = None # Check for re-export source (export { x } from './other') source_node = node.child_by_field_name("source") @@ -726,6 +730,12 @@ def _extract_export_info(self, node: Node, source_bytes: bytes) -> ExportInfo | default_export = self.get_node_text(sibling, source_bytes) elif sibling.type in ("arrow_function", "function_expression", "object", "array"): default_export = "default" + elif sibling.type == "call_expression": + # Handle wrapped exports: export default curry(traverseEntity) + # The default export is the result of the call, but we track + # the wrapped function names for export checking + default_export = "default" + wrapped_default_args = self._extract_call_expression_identifiers(sibling, source_bytes) break # Handle named exports: export { a, b as c } @@ -773,8 +783,37 @@ def _extract_export_info(self, node: Node, source_bytes: bytes) -> ExportInfo | reexport_source=reexport_source, start_line=node.start_point[0] + 1, end_line=node.end_point[0] + 1, + wrapped_default_args=wrapped_default_args, ) + def _extract_call_expression_identifiers(self, node: Node, source_bytes: bytes) -> list[str]: + """Extract identifier names from arguments of a call expression. + + For patterns like curry(traverseEntity) or compose(fn1, fn2), this extracts + the function names passed as arguments: ["traverseEntity"] or ["fn1", "fn2"]. + + Args: + node: A call_expression node. + source_bytes: The source code as bytes. + + Returns: + List of identifier names found in the call arguments. + + """ + identifiers: list[str] = [] + + # Get the arguments node + args_node = node.child_by_field_name("arguments") + if args_node: + for child in args_node.children: + if child.type == "identifier": + identifiers.append(self.get_node_text(child, source_bytes)) + # Also handle nested call expressions: compose(curry(fn)) + elif child.type == "call_expression": + identifiers.extend(self._extract_call_expression_identifiers(child, source_bytes)) + + return identifiers + def _extract_commonjs_export(self, node: Node, source_bytes: bytes) -> ExportInfo | None: """Extract export information from CommonJS module.exports or exports.* patterns. @@ -876,6 +915,7 @@ def is_function_exported( """Check if a function is exported and get its export name. For class methods, also checks if the containing class is exported. + Also handles wrapped exports like: export default curry(traverseEntity) Args: source: The source code to analyze. @@ -901,6 +941,11 @@ def is_function_exported( if name == function_name: return (True, alias if alias else name) + # Check wrapped default exports: export default curry(traverseEntity) + # The function is exported via wrapper, so it's accessible as "default" + if export.wrapped_default_args and function_name in export.wrapped_default_args: + return (True, "default") + # For class methods, check if the containing class is exported if class_name: for export in exports: diff --git a/codeflash/languages/javascript/vitest_runner.py b/codeflash/languages/javascript/vitest_runner.py index 47a529dae..d169752bc 100644 --- a/codeflash/languages/javascript/vitest_runner.py +++ b/codeflash/languages/javascript/vitest_runner.py @@ -23,8 +23,13 @@ def _find_vitest_project_root(file_path: Path) -> Path | None: """Find the Vitest project root by looking for vitest/vite config or package.json. - Traverses up from the given file path to find the nearest directory - containing vitest.config.js/ts, vite.config.js/ts, or package.json. + Traverses up from the given file path to find the directory containing + vitest.config.js/ts or vite.config.js/ts. Falls back to package.json only + if no vitest/vite config is found in any parent directory. + + In monorepos, package.json may exist at multiple levels (e.g., packages/lib/package.json), + but the vitest config with setupFiles is typically at the monorepo root. + We need to prioritize finding the actual vitest config to ensure paths resolve correctly. Args: file_path: A file path within the Vitest project. @@ -34,8 +39,10 @@ def _find_vitest_project_root(file_path: Path) -> Path | None: """ current = file_path.parent if file_path.is_file() else file_path + package_json_dir = None # Track first package.json found (fallback) + while current != current.parent: # Stop at filesystem root - # Check for Vitest-specific config files first + # Check for Vitest-specific config files first - these should take priority if ( (current / "vitest.config.js").exists() or (current / "vitest.config.ts").exists() @@ -45,27 +52,40 @@ def _find_vitest_project_root(file_path: Path) -> Path | None: or (current / "vite.config.ts").exists() or (current / "vite.config.mjs").exists() or (current / "vite.config.mts").exists() - or (current / "package.json").exists() ): return current + # Remember first package.json as fallback, but keep looking for vitest config + if package_json_dir is None and (current / "package.json").exists(): + package_json_dir = current current = current.parent - return None + + # No vitest config found, fall back to package.json directory if found + return package_json_dir def _is_vitest_coverage_available(project_root: Path) -> bool: """Check if Vitest coverage package is available. + In monorepos, dependencies may be hoisted to the root node_modules. + This function searches up the directory tree for the coverage package. + Args: - project_root: The project root directory. + project_root: The project root directory (may be a package in a monorepo). Returns: True if @vitest/coverage-v8 or @vitest/coverage-istanbul is installed. """ - node_modules = project_root / "node_modules" - return (node_modules / "@vitest" / "coverage-v8").exists() or ( - node_modules / "@vitest" / "coverage-istanbul" - ).exists() + current = project_root + while current != current.parent: # Stop at filesystem root + node_modules = current / "node_modules" + if node_modules.exists(): + if (node_modules / "@vitest" / "coverage-v8").exists() or ( + node_modules / "@vitest" / "coverage-istanbul" + ).exists(): + return True + current = current.parent + return False def _ensure_runtime_files(project_root: Path) -> None: @@ -97,8 +117,146 @@ def _ensure_runtime_files(project_root: Path) -> None: logger.error(f"Could not install codeflash. Please install it manually: {' '.join(install_cmd)}") +def _find_monorepo_root(start_path: Path) -> Path | None: + """Find the monorepo root by looking for workspace markers. + + Args: + start_path: A path within the monorepo. + + Returns: + The monorepo root directory, or None if not found. + + """ + monorepo_markers = ["pnpm-workspace.yaml", "yarn.lock", "lerna.json", "package-lock.json"] + current = start_path if start_path.is_dir() else start_path.parent + + while current != current.parent: + # Check for monorepo markers + if any((current / marker).exists() for marker in monorepo_markers): + # Verify it has node_modules or package.json (it's a real root) + if (current / "node_modules").exists() or (current / "package.json").exists(): + return current + current = current.parent + + return None + + +def _is_vitest_workspace(project_root: Path) -> bool: + """Check if the project uses vitest workspace configuration. + + Vitest workspaces have a special structure where the root config + points to package-level configs. We shouldn't override these. + + Args: + project_root: The project root directory. + + Returns: + True if the project appears to use vitest workspace. + + """ + vitest_config = project_root / "vitest.config.ts" + if not vitest_config.exists(): + vitest_config = project_root / "vitest.config.js" + if not vitest_config.exists(): + return False + + try: + content = vitest_config.read_text() + # Check for workspace indicators + return "workspace" in content.lower() or "defineWorkspace" in content + except Exception: + return False + + +def _ensure_codeflash_vitest_config(project_root: Path) -> Path | None: + """Create or find a Codeflash-compatible Vitest config. + + Vitest configs often have restrictive include patterns like 'test/**/*.test.ts' + which filter out our generated test files. This function creates a config + that overrides the include pattern to accept all test files. + + Note: For workspace projects, we skip creating a custom config as it would + conflict with the workspace setup. In those cases, tests should be placed + in the correct package's test directory. + + Args: + project_root: The project root directory. + + Returns: + Path to the Codeflash Vitest config, or None if creation failed/not needed. + + """ + # Check for workspace configuration - don't override these + monorepo_root = _find_monorepo_root(project_root) + if monorepo_root and _is_vitest_workspace(monorepo_root): + logger.debug("Detected vitest workspace configuration - skipping custom config") + return None + + codeflash_config_path = project_root / "codeflash.vitest.config.mjs" + + # If already exists, use it + if codeflash_config_path.exists(): + logger.debug(f"Using existing Codeflash Vitest config: {codeflash_config_path}") + return codeflash_config_path + + # Find the original vitest config to extend + original_config = None + for config_name in ["vitest.config.ts", "vitest.config.js", "vitest.config.mts", "vitest.config.mjs"]: + config_path = project_root / config_name + if config_path.exists(): + original_config = config_name + break + + # Also check for vite config with vitest settings + if not original_config: + for config_name in ["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"]: + config_path = project_root / config_name + if config_path.exists(): + original_config = config_name + break + + # Create a config that extends the original and overrides include pattern + if original_config: + config_content = f"""// Auto-generated by Codeflash for test file pattern compatibility +import {{ mergeConfig }} from 'vitest/config'; +import originalConfig from './{original_config}'; + +export default mergeConfig(originalConfig, {{ + test: {{ + // Override include pattern to match all test files including generated ones + include: ['**/*.test.ts', '**/*.test.js', '**/*.test.tsx', '**/*.test.jsx'], + }}, +}}); +""" + else: + # No original config found, create a minimal one + config_content = """// Auto-generated by Codeflash for test file pattern compatibility +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // Include all test files including generated ones + include: ['**/*.test.ts', '**/*.test.js', '**/*.test.tsx', '**/*.test.jsx'], + // Exclude common non-test directories + exclude: ['**/node_modules/**', '**/dist/**'], + }, +}); +""" + + try: + codeflash_config_path.write_text(config_content) + logger.debug(f"Created Codeflash Vitest config: {codeflash_config_path}") + return codeflash_config_path + except Exception as e: + logger.warning(f"Failed to create Codeflash Vitest config: {e}") + return None + + def _build_vitest_behavioral_command( - test_files: list[Path], timeout: int | None = None, output_file: Path | None = None + test_files: list[Path], + timeout: int | None = None, + output_file: Path | None = None, + project_root: Path | None = None, ) -> list[str]: """Build Vitest command for behavioral tests. @@ -106,6 +264,7 @@ def _build_vitest_behavioral_command( test_files: List of test files to run. timeout: Optional timeout in seconds. output_file: Optional path for JUnit XML output. + project_root: Project root directory for --root flag. Returns: Command list for subprocess execution. @@ -120,8 +279,18 @@ def _build_vitest_behavioral_command( "--no-file-parallelism", # Serial execution for deterministic timing ] + # For monorepos with restrictive vitest configs (e.g., include: test/**/*.test.ts), + # we need to create a custom config that allows all test patterns. + # This is done by creating a codeflash.vitest.config.mjs file. + if project_root: + codeflash_vitest_config = _ensure_codeflash_vitest_config(project_root) + if codeflash_vitest_config: + cmd.append(f"--config={codeflash_vitest_config}") + if output_file: - cmd.append(f"--outputFile={output_file}") + # Use dot notation for junit reporter output file when multiple reporters are used + # Format: --outputFile.junit=/path/to/file.xml + cmd.append(f"--outputFile.junit={output_file}") if timeout: cmd.append(f"--test-timeout={timeout * 1000}") # Vitest uses milliseconds @@ -133,7 +302,10 @@ def _build_vitest_behavioral_command( def _build_vitest_benchmarking_command( - test_files: list[Path], timeout: int | None = None, output_file: Path | None = None + test_files: list[Path], + timeout: int | None = None, + output_file: Path | None = None, + project_root: Path | None = None, ) -> list[str]: """Build Vitest command for benchmarking tests. @@ -141,6 +313,7 @@ def _build_vitest_benchmarking_command( test_files: List of test files to run. timeout: Optional timeout in seconds. output_file: Optional path for JUnit XML output. + project_root: Project root directory for --root flag. Returns: Command list for subprocess execution. @@ -155,8 +328,15 @@ def _build_vitest_benchmarking_command( "--no-file-parallelism", # Serial execution for consistent benchmarking ] + # Use codeflash vitest config to override restrictive include patterns + if project_root: + codeflash_vitest_config = _ensure_codeflash_vitest_config(project_root) + if codeflash_vitest_config: + cmd.append(f"--config={codeflash_vitest_config}") + if output_file: - cmd.append(f"--outputFile={output_file}") + # Use dot notation for junit reporter output file when multiple reporters are used + cmd.append(f"--outputFile.junit={output_file}") if timeout: cmd.append(f"--test-timeout={timeout * 1000}") @@ -217,11 +397,20 @@ def run_vitest_behavioral_tests( logger.debug("Vitest coverage package not installed, running without coverage") # Build Vitest command - vitest_cmd = _build_vitest_behavioral_command(test_files=test_files, timeout=timeout, output_file=result_file_path) + vitest_cmd = _build_vitest_behavioral_command( + test_files=test_files, timeout=timeout, output_file=result_file_path, project_root=effective_cwd + ) # Add coverage flags only if coverage is available if coverage_available: + # Don't pre-create the coverage directory - vitest should create it + # Pre-creating an empty directory may cause vitest to delete it + logger.debug(f"Coverage will be written to: {coverage_dir}") + vitest_cmd.extend(["--coverage", "--coverage.reporter=json", f"--coverage.reportsDirectory={coverage_dir}"]) + # Note: Removed --coverage.enabled=true (redundant) and --coverage.all false + # The version mismatch between vitest and @vitest/coverage-v8 can cause + # issues with coverage flag parsing. Let vitest use default settings. # Set up environment vitest_env = test_env.copy() @@ -248,6 +437,7 @@ def run_vitest_behavioral_tests( cwd=effective_cwd, env=vitest_env, timeout=subprocess_timeout, check=False, text=True, capture_output=True ) result = subprocess.run(vitest_cmd, **run_args) # noqa: PLW1510 + # Combine stderr into stdout for timing markers if result.stderr and not result.stdout: result = subprocess.CompletedProcess( @@ -258,6 +448,13 @@ def run_vitest_behavioral_tests( args=result.args, returncode=result.returncode, stdout=result.stdout + "\n" + result.stderr, stderr="" ) logger.debug(f"Vitest result: returncode={result.returncode}") + # Log detailed output if tests fail or no XML output + if result.returncode != 0: + logger.warning( + f"Vitest failed with returncode={result.returncode}.\n" + f"Command: {' '.join(vitest_cmd)}\n" + f"Stdout: {result.stdout[:2000] if result.stdout else '(empty)'}" + ) except subprocess.TimeoutExpired: logger.warning(f"Vitest tests timed out after {subprocess_timeout}s") result = subprocess.CompletedProcess( @@ -272,6 +469,40 @@ def run_vitest_behavioral_tests( wall_clock_ns = time.perf_counter_ns() - start_time_ns logger.debug(f"Vitest behavioral tests completed in {wall_clock_ns / 1e9:.2f}s") + # Check if JUnit XML was created and has content + if result_file_path.exists(): + file_size = result_file_path.stat().st_size + logger.debug(f"Vitest JUnit XML created: {result_file_path} ({file_size} bytes)") + if file_size < 200: # Suspiciously small - likely empty or just headers + logger.warning( + f"Vitest JUnit XML is very small ({file_size} bytes). Content: {result_file_path.read_text()[:500]}" + ) + else: + logger.warning( + f"Vitest JUnit XML not created at {result_file_path}. " + f"Vitest stdout: {result.stdout[:1000] if result.stdout else '(empty)'}" + ) + + # Check if coverage file was created + if coverage_available and coverage_json_path: + if coverage_json_path.exists(): + cov_size = coverage_json_path.stat().st_size + logger.debug(f"Vitest coverage JSON created: {coverage_json_path} ({cov_size} bytes)") + else: + # Check if the parent directory exists and list its contents + cov_parent = coverage_json_path.parent + if cov_parent.exists(): + contents = list(cov_parent.iterdir()) + logger.warning( + f"Vitest coverage JSON not created at {coverage_json_path}. " + f"Directory exists with contents: {[f.name for f in contents]}" + ) + else: + logger.warning( + f"Vitest coverage JSON not created at {coverage_json_path}. " + f"Coverage directory does not exist: {cov_parent}" + ) + return result_file_path, result, coverage_json_path, None @@ -289,6 +520,9 @@ def run_vitest_benchmarking_tests( ) -> tuple[Path, subprocess.CompletedProcess]: """Run Vitest benchmarking tests with external looping from Python. + NOTE: This function MUST use benchmarking_file_path (perf tests with capturePerf), + NOT instrumented_behavior_file_path (behavior tests with capture). + Uses external process-level looping to run tests multiple times and collect timing data. This matches the Python pytest approach where looping is controlled externally for simplicity. @@ -313,6 +547,26 @@ def run_vitest_benchmarking_tests( # Get performance test files test_files = [Path(file.benchmarking_file_path) for file in test_paths.test_files if file.benchmarking_file_path] + # Log test file selection + total_test_files = len(test_paths.test_files) + perf_test_files = len(test_files) + logger.debug( + f"Vitest benchmark test file selection: {perf_test_files}/{total_test_files} have benchmarking_file_path" + ) + if perf_test_files == 0: + logger.warning("No perf test files found! Cannot run benchmarking tests.") + for tf in test_paths.test_files: + logger.warning( + f"Test file: behavior={tf.instrumented_behavior_file_path}, perf={tf.benchmarking_file_path}" + ) + elif perf_test_files < total_test_files: + for tf in test_paths.test_files: + if not tf.benchmarking_file_path: + logger.warning(f"Missing benchmarking_file_path: behavior={tf.instrumented_behavior_file_path}") + else: + for tf in test_files[:3]: # Log first 3 perf test files + logger.debug(f"Using perf test file: {tf}") + # Use provided project_root, or detect it as fallback if project_root is None and test_files: project_root = _find_vitest_project_root(test_files[0]) @@ -325,7 +579,7 @@ def run_vitest_benchmarking_tests( # Build Vitest command for performance tests vitest_cmd = _build_vitest_benchmarking_command( - test_files=test_files, timeout=timeout, output_file=result_file_path + test_files=test_files, timeout=timeout, output_file=result_file_path, project_root=effective_cwd ) # Base environment setup @@ -343,14 +597,25 @@ def run_vitest_benchmarking_tests( vitest_env["CODEFLASH_PERF_STABILITY_CHECK"] = "true" if stability_check else "false" vitest_env["CODEFLASH_LOOP_INDEX"] = "1" + # Set test module for marker identification (use first test file as reference) + if test_files: + test_module_path = str( + test_files[0].relative_to(effective_cwd) + if test_files[0].is_relative_to(effective_cwd) + else test_files[0].name + ) + vitest_env["CODEFLASH_TEST_MODULE"] = test_module_path + logger.debug(f"[VITEST-BENCH] Set CODEFLASH_TEST_MODULE={test_module_path}") + # Total timeout for the entire benchmark run total_timeout = max(120, (target_duration_ms // 1000) + 60, timeout or 120) - logger.debug(f"Running Vitest benchmarking tests: {' '.join(vitest_cmd)}") + logger.debug(f"[VITEST-BENCH] Running Vitest benchmarking tests: {' '.join(vitest_cmd)}") logger.debug( - f"Vitest benchmarking config: min_loops={min_loops}, max_loops={max_loops}, " + f"[VITEST-BENCH] Config: min_loops={min_loops}, max_loops={max_loops}, " f"target_duration={target_duration_ms}ms, stability_check={stability_check}" ) + logger.debug(f"[VITEST-BENCH] Environment: CODEFLASH_PERF_LOOP_COUNT={vitest_env.get('CODEFLASH_PERF_LOOP_COUNT')}") total_start_time = time.time() @@ -375,7 +640,27 @@ def run_vitest_benchmarking_tests( result = subprocess.CompletedProcess(args=vitest_cmd, returncode=-1, stdout="", stderr="Vitest not found") wall_clock_seconds = time.time() - total_start_time - logger.debug(f"Vitest benchmarking completed in {wall_clock_seconds:.2f}s") + logger.debug(f"[VITEST-BENCH] Completed in {wall_clock_seconds:.2f}s, returncode={result.returncode}") + + # Debug: Check for END markers with duration (perf test format) + if result.stdout: + import re + + perf_end_pattern = re.compile(r"!######[^:]+:[^:]+:[^:]+:(\d+):[^:]+:(\d+)######!") + perf_matches = list(perf_end_pattern.finditer(result.stdout)) + if perf_matches: + loop_indices = [int(m.group(1)) for m in perf_matches] + logger.debug( + f"[VITEST-BENCH] Found {len(perf_matches)} perf END markers in stdout, " + f"loop_index range: {min(loop_indices)}-{max(loop_indices)}" + ) + else: + logger.debug(f"[VITEST-BENCH] No perf END markers found in stdout (len={len(result.stdout)})") + # Check if there are behavior END markers instead + behavior_end_pattern = re.compile(r"!######[^:]+:[^:]+:[^:]+:\d+:[^#]+######!") + behavior_matches = list(behavior_end_pattern.finditer(result.stdout)) + if behavior_matches: + logger.debug(f"[VITEST-BENCH] Found {len(behavior_matches)} behavior END markers instead (no duration)") return result_file_path, result @@ -436,7 +721,14 @@ def run_vitest_line_profile_tests( "--no-file-parallelism", # Serial execution for consistent line profiling ] - vitest_cmd.append(f"--outputFile={result_file_path}") + # Use codeflash vitest config to override restrictive include patterns + if effective_cwd: + codeflash_vitest_config = _ensure_codeflash_vitest_config(effective_cwd) + if codeflash_vitest_config: + vitest_cmd.append(f"--config={codeflash_vitest_config}") + + # Use dot notation for junit reporter output file when multiple reporters are used + vitest_cmd.append(f"--outputFile.junit={result_file_path}") if timeout: vitest_cmd.append(f"--test-timeout={timeout * 1000}") diff --git a/codeflash/models/models.py b/codeflash/models/models.py index 02192d7df..252c55541 100644 --- a/codeflash/models/models.py +++ b/codeflash/models/models.py @@ -23,7 +23,7 @@ from enum import Enum, IntEnum from pathlib import Path from re import Pattern -from typing import NamedTuple, Optional, cast +from typing import Any, NamedTuple, Optional, cast from jedi.api.classes import Name from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, ValidationError, model_validator @@ -172,7 +172,7 @@ class BestOptimization(BaseModel): winning_behavior_test_results: TestResults winning_benchmarking_test_results: TestResults winning_replay_benchmarking_test_results: Optional[TestResults] = None - line_profiler_test_results: dict + line_profiler_test_results: dict[Any, Any] async_throughput: Optional[int] = None concurrency_metrics: Optional[ConcurrencyMetrics] = None @@ -209,7 +209,7 @@ def to_string(self) -> str: f"Benchmark speedup for {self.benchmark_name}::{self.test_function}: {self.speedup_percent:.2f}%\n" ) - def to_dict(self) -> dict[str, any]: + def to_dict(self) -> dict[str, Any]: return { "benchmark_name": self.benchmark_name, "test_function": self.test_function, @@ -232,20 +232,28 @@ def to_string(self) -> str: result += detail.to_string() + "\n" return result - def to_dict(self) -> dict[str, list[dict[str, any]]]: + def to_dict(self) -> dict[str, list[dict[str, Any]]]: return {"benchmark_details": [detail.to_dict() for detail in self.benchmark_details]} class CodeString(BaseModel): code: str file_path: Optional[Path] = None - language: str = "python" # Language for validation - only Python code is validated + language: str = "python" # Language for validation @model_validator(mode="after") def validate_code_syntax(self) -> CodeString: - """Validate code syntax for Python only.""" + """Validate code syntax for the specified language.""" if self.language == "python": validate_python_code(self.code) + elif self.language in ("javascript", "typescript"): + # Validate JavaScript/TypeScript syntax using language support + from codeflash.languages.registry import get_language_support + + lang_support = get_language_support(self.language) + if not lang_support.validate_syntax(self.code): + msg = f"Invalid {self.language.title()} code" + raise ValueError(msg) return self @@ -272,7 +280,7 @@ def get_code_block_splitter(file_path: Path | None) -> str: class CodeStringsMarkdown(BaseModel): code_strings: list[CodeString] = [] language: str = "python" # Language for markdown code block tags - _cache: dict = PrivateAttr(default_factory=dict) + _cache: dict[str, Any] = PrivateAttr(default_factory=dict) @property def flat(self) -> str: @@ -447,6 +455,19 @@ def get_test_type_by_instrumented_file_path(self, file_path: Path) -> TestType | normalized_benchmark_path = self._normalize_path_for_comparison(test_file.benchmarking_file_path) if normalized == normalized_benchmark_path: return test_file.test_type + + # Fallback: try filename-only matching for JavaScript/TypeScript + # Jest/Vitest JUnit XML may have relative paths that don't match absolute paths + file_name = file_path.name + for test_file in self.test_files: + if ( + test_file.instrumented_behavior_file_path + and test_file.instrumented_behavior_file_path.name == file_name + ): + return test_file.test_type + if test_file.benchmarking_file_path and test_file.benchmarking_file_path.name == file_name: + return test_file.test_type + return None def get_test_type_by_original_file_path(self, file_path: Path) -> TestType | None: diff --git a/codeflash/optimization/function_optimizer.py b/codeflash/optimization/function_optimizer.py index f9c92a664..9f7169740 100644 --- a/codeflash/optimization/function_optimizer.py +++ b/codeflash/optimization/function_optimizer.py @@ -605,15 +605,24 @@ def generate_and_instrument_tests( ]: """Generate and instrument tests for the function.""" n_tests = get_effort_value(EffortKeys.N_GENERATED_TESTS, self.effort) + source_file = Path(self.function_to_optimize.file_path) generated_test_paths = [ get_test_file_path( - self.test_cfg.tests_root, self.function_to_optimize.function_name, test_index, test_type="unit" + self.test_cfg.tests_root, + self.function_to_optimize.function_name, + test_index, + test_type="unit", + source_file_path=source_file, ) for test_index in range(n_tests) ] generated_perf_test_paths = [ get_test_file_path( - self.test_cfg.tests_root, self.function_to_optimize.function_name, test_index, test_type="perf" + self.test_cfg.tests_root, + self.function_to_optimize.function_name, + test_index, + test_type="perf", + source_file_path=source_file, ) for test_index in range(n_tests) ] @@ -638,7 +647,7 @@ def generate_and_instrument_tests( if is_javascript(): module_system = detect_module_system(self.project_root, self.function_to_optimize.file_path) if module_system == "esm": - generated_tests = inject_test_globals(generated_tests) + generated_tests = inject_test_globals(generated_tests, self.test_cfg.test_framework) if is_typescript(): # disable ts check for typescript tests generated_tests = disable_ts_check(generated_tests) @@ -2248,10 +2257,11 @@ def setup_and_establish_baseline( return Failure(baseline_result.failure()) original_code_baseline, test_functions_to_remove = baseline_result.unwrap() - if isinstance(original_code_baseline, OriginalCodeBaseline) and ( - not coverage_critic(original_code_baseline.coverage_results) - or not quantity_of_tests_critic(original_code_baseline) - ): + # Check test quantity for all languages + quantity_ok = quantity_of_tests_critic(original_code_baseline) + # TODO: {Self} Only check coverage for Python - coverage infrastructure not yet reliable for JS/TS + coverage_ok = coverage_critic(original_code_baseline.coverage_results) if is_python() else True + if isinstance(original_code_baseline, OriginalCodeBaseline) and (not coverage_ok or not quantity_ok): if self.args.override_fixtures: restore_conftest(original_conftest_content) cleanup_paths(paths_to_cleanup) @@ -2695,6 +2705,12 @@ def establish_original_code_baseline( ) console.rule() with progress_bar("Running performance benchmarks..."): + logger.debug( + f"[BENCHMARK-START] Starting benchmarking tests with {len(self.test_files.test_files)} test files" + ) + for idx, tf in enumerate(self.test_files.test_files): + logger.debug(f"[BENCHMARK-FILES] Test file {idx}: perf_file={tf.benchmarking_file_path}") + if self.function_to_optimize.is_async and is_python(): from codeflash.code_utils.instrument_existing_tests import add_async_decorator_to_function @@ -2712,6 +2728,7 @@ def establish_original_code_baseline( enable_coverage=False, code_context=code_context, ) + logger.debug(f"[BENCHMARK-DONE] Got {len(benchmarking_results.test_results)} benchmark results") finally: if self.function_to_optimize.is_async: self.write_code_and_helpers( @@ -2914,8 +2931,7 @@ def run_optimized_candidate( total_passed = sum(r.get("passed", 0) for r in candidate_report.values()) if total_passed == 0: logger.warning( - "No behavioral tests passed for optimization candidate %d. " - "Skipping correctness verification.", + "No behavioral tests passed for optimization candidate %d. Skipping correctness verification.", optimization_candidate_index, ) return self.get_results_not_matched_error() @@ -3144,7 +3160,7 @@ def run_and_parse_tests( coverage_database_file=coverage_database_file, coverage_config_file=coverage_config_file, skip_sqlite_cleanup=skip_cleanup, - testing_type=testing_type + testing_type=testing_type, ) if testing_type == TestingMode.PERFORMANCE: results.perf_stdout = run_result.stdout diff --git a/codeflash/verification/comparator.py b/codeflash/verification/comparator.py index ad7c59ede..6429b5520 100644 --- a/codeflash/verification/comparator.py +++ b/codeflash/verification/comparator.py @@ -6,6 +6,7 @@ import math import re import types +import weakref from collections import ChainMap, OrderedDict, deque from importlib.util import find_spec from typing import Any, Optional @@ -25,6 +26,7 @@ HAS_XARRAY = find_spec("xarray") is not None HAS_TENSORFLOW = find_spec("tensorflow") is not None HAS_NUMBA = find_spec("numba") is not None +HAS_PYARROW = find_spec("pyarrow") is not None # Pattern to match pytest temp directories: /tmp/pytest-of-/pytest-/ # These paths vary between test runs but are logically equivalent @@ -93,7 +95,7 @@ def _get_wrapped_exception(exc: BaseException) -> Optional[BaseException]: # no return _extract_exception_from_message(str(exc)) -def comparator(orig: Any, new: Any, superset_obj=False) -> bool: +def comparator(orig: Any, new: Any, superset_obj: bool = False) -> bool: """Compare two objects for equality recursively. If superset_obj is True, the new object is allowed to have more keys than the original object. However, the existing keys/values must be equivalent.""" try: # Handle exceptions specially - before type check to allow wrapper comparison @@ -171,6 +173,17 @@ def comparator(orig: Any, new: Any, superset_obj=False) -> bool: return True return math.isclose(orig, new) + # Handle weak references (e.g., found in torch.nn.LSTM/GRU modules) + if isinstance(orig, weakref.ref): + orig_referent = orig() + new_referent = new() + # Both dead refs are equal, otherwise compare referents + if orig_referent is None and new_referent is None: + return True + if orig_referent is None or new_referent is None: + return False + return comparator(orig_referent, new_referent, superset_obj) + if HAS_JAX: import jax # type: ignore # noqa: PGH003 import jax.numpy as jnp # type: ignore # noqa: PGH003 @@ -342,13 +355,57 @@ def comparator(orig: Any, new: Any, superset_obj=False) -> bool: return False return (orig != new).nnz == 0 + if HAS_PYARROW: + import pyarrow as pa # type: ignore # noqa: PGH003 + + if isinstance(orig, pa.Table): + if orig.schema != new.schema: + return False + if orig.num_rows != new.num_rows: + return False + return bool(orig.equals(new)) + + if isinstance(orig, pa.RecordBatch): + if orig.schema != new.schema: + return False + if orig.num_rows != new.num_rows: + return False + return bool(orig.equals(new)) + + if isinstance(orig, pa.ChunkedArray): + if orig.type != new.type: + return False + if len(orig) != len(new): + return False + return bool(orig.equals(new)) + + if isinstance(orig, pa.Array): + if orig.type != new.type: + return False + if len(orig) != len(new): + return False + return bool(orig.equals(new)) + + if isinstance(orig, pa.Scalar): + if orig.type != new.type: + return False + # Handle null scalars + if not orig.is_valid and not new.is_valid: + return True + if not orig.is_valid or not new.is_valid: + return False + return bool(orig.equals(new)) + + if isinstance(orig, (pa.Schema, pa.Field, pa.DataType)): + return bool(orig.equals(new)) + if HAS_PANDAS: import pandas # noqa: ICN001 if isinstance( orig, (pandas.DataFrame, pandas.Series, pandas.Index, pandas.Categorical, pandas.arrays.SparseArray) ): - return orig.equals(new) + return bool(orig.equals(new)) if isinstance(orig, (pandas.CategoricalDtype, pandas.Interval, pandas.Period)): return orig == new @@ -395,10 +452,10 @@ def comparator(orig: Any, new: Any, superset_obj=False) -> bool: return orig == new if HAS_NUMBA: - import numba # type: ignore # noqa: PGH003 - from numba.core.dispatcher import Dispatcher # type: ignore # noqa: PGH003 - from numba.typed import Dict as NumbaDict # type: ignore # noqa: PGH003 - from numba.typed import List as NumbaList # type: ignore # noqa: PGH003 + import numba + from numba.core.dispatcher import Dispatcher + from numba.typed import Dict as NumbaDict + from numba.typed import List as NumbaList # Handle numba typed List if isinstance(orig, NumbaList): diff --git a/codeflash/verification/parse_test_output.py b/codeflash/verification/parse_test_output.py index 44101e4c3..ace803098 100644 --- a/codeflash/verification/parse_test_output.py +++ b/codeflash/verification/parse_test_output.py @@ -1,6 +1,5 @@ from __future__ import annotations -import contextlib import os import re import sqlite3 @@ -22,14 +21,17 @@ ) from codeflash.discovery.discover_unit_tests import discover_parameters_unittest from codeflash.languages import is_java, is_javascript, is_python + +# Import Jest-specific parsing from the JavaScript language module +from codeflash.languages.javascript.parse import parse_jest_test_xml as _parse_jest_test_xml from codeflash.models.models import ( ConcurrencyMetrics, FunctionTestInvocation, InvocationId, + TestingMode, TestResults, TestType, VerificationType, - TestingMode, ) from codeflash.verification.coverage_utils import CoverageUtils, JacocoCoverageUtils, JestCoverageUtils @@ -53,11 +55,8 @@ def parse_func(file_path: Path) -> XMLParser: start_pattern = re.compile(r"!\$######([^:]*):([^:]*):([^:]*):([^:]*):([^:]+)######\$!") end_pattern = re.compile(r"!######([^:]*):([^:]*):([^:]*):([^:]*):([^:]+):([^:]+)######!") -# Jest timing marker patterns (from codeflash-jest-helper.js console.log output) -# Format: !$######testName:testName:funcName:loopIndex:lineId######$! (start) -# Format: !######testName:testName:funcName:loopIndex:lineId:durationNs######! (end) -jest_start_pattern = re.compile(r"!\$######([^:]+):([^:]+):([^:]+):([^:]+):([^#]+)######\$!") -jest_end_pattern = re.compile(r"!######([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):(\d+)######!") +# Jest timing marker patterns are imported from codeflash.languages.javascript.parse +# and re-exported here for backwards compatibility def calculate_function_throughput_from_test_results(test_results: TestResults, function_name: str) -> int: @@ -629,356 +628,6 @@ def parse_sqlite_test_results(sqlite_file_path: Path, test_files: TestFiles, tes return test_results -def _extract_jest_console_output(suite_elem) -> str: - """Extract console output from Jest's JUnit XML system-out element. - - Jest-junit writes console.log output as a JSON array in the testsuite's system-out. - Each entry has: {"message": "...", "origin": "...", "type": "log"} - - Args: - suite_elem: The testsuite lxml element - - Returns: - Concatenated message content from all log entries - - """ - import json - - system_out_elem = suite_elem.find("system-out") - if system_out_elem is None or system_out_elem.text is None: - return "" - - raw_content = system_out_elem.text.strip() - if not raw_content: - return "" - - # Jest-junit wraps console output in a JSON array - # Try to parse as JSON first - try: - log_entries = json.loads(raw_content) - if isinstance(log_entries, list): - # Extract message field from each log entry - messages = [] - for entry in log_entries: - if isinstance(entry, dict) and "message" in entry: - messages.append(entry["message"]) - return "\n".join(messages) - except (json.JSONDecodeError, TypeError): - # Not JSON - return as plain text (fallback for pytest-style output) - pass - - return raw_content - - -# TODO: {Claude} we need to move to the support directory. -def parse_jest_test_xml( - test_xml_file_path: Path, - test_files: TestFiles, - test_config: TestConfig, - run_result: subprocess.CompletedProcess | None = None, -) -> TestResults: - """Parse Jest JUnit XML test results. - - Jest-junit has a different structure than pytest: - - system-out is at the testsuite level (not testcase) - - system-out contains a JSON array of log entries - - Timing markers are in the message field of log entries - - Args: - test_xml_file_path: Path to the Jest JUnit XML file - test_files: TestFiles object with test file information - test_config: Test configuration - run_result: Optional subprocess result for logging - - Returns: - TestResults containing parsed test invocations - - """ - test_results = TestResults() - - if not test_xml_file_path.exists(): - logger.warning(f"No JavaScript test results for {test_xml_file_path} found.") - return test_results - - # Log file size for debugging - file_size = test_xml_file_path.stat().st_size - logger.debug(f"Jest XML file size: {file_size} bytes at {test_xml_file_path}") - - try: - xml = JUnitXml.fromfile(str(test_xml_file_path), parse_func=parse_func) - logger.debug(f"Successfully parsed Jest JUnit XML from {test_xml_file_path}") - except Exception as e: - logger.warning(f"Failed to parse {test_xml_file_path} as JUnitXml. Exception: {e}") - return test_results - - base_dir = test_config.tests_project_rootdir - logger.debug(f"Jest XML parsing: base_dir={base_dir}, num_test_files={len(test_files.test_files)}") - - # Build lookup from instrumented file path to TestFile for direct matching - # This handles cases where instrumented files are in temp directories - instrumented_path_lookup: dict[str, tuple[Path, TestType]] = {} - for test_file in test_files.test_files: - if test_file.instrumented_behavior_file_path: - # Store both the absolute path and resolved path as keys - abs_path = str(test_file.instrumented_behavior_file_path.resolve()) - instrumented_path_lookup[abs_path] = (test_file.instrumented_behavior_file_path, test_file.test_type) - # Also store the string representation in case of minor path differences - instrumented_path_lookup[str(test_file.instrumented_behavior_file_path)] = ( - test_file.instrumented_behavior_file_path, - test_file.test_type, - ) - logger.debug(f"Jest XML lookup: registered {abs_path}") - - # Fallback: if JUnit XML doesn't have system-out, use subprocess stdout directly - global_stdout = "" - if run_result is not None: - try: - global_stdout = run_result.stdout if isinstance(run_result.stdout, str) else run_result.stdout.decode() - # Debug: log if timing markers are found in stdout - if global_stdout: - marker_count = len(jest_start_pattern.findall(global_stdout)) - if marker_count > 0: - logger.debug(f"Found {marker_count} timing start markers in Jest stdout") - else: - logger.debug(f"No timing start markers found in Jest stdout (len={len(global_stdout)})") - except (AttributeError, UnicodeDecodeError): - global_stdout = "" - - suite_count = 0 - testcase_count = 0 - for suite in xml: - suite_count += 1 - # Extract console output from suite-level system-out (Jest specific) - suite_stdout = _extract_jest_console_output(suite._elem) # noqa: SLF001 - - # Fallback: use subprocess stdout if XML system-out is empty - if not suite_stdout and global_stdout: - suite_stdout = global_stdout - - # Parse timing markers from the suite's console output - start_matches = list(jest_start_pattern.finditer(suite_stdout)) - end_matches_dict = {} - for match in jest_end_pattern.finditer(suite_stdout): - # Key: (testName, testName2, funcName, loopIndex, lineId) - key = match.groups()[:5] - end_matches_dict[key] = match - - for testcase in suite: - testcase_count += 1 - test_class_path = testcase.classname # For Jest, this is the file path - test_name = testcase.name - - if test_name is None: - logger.debug(f"testcase.name is None in Jest XML {test_xml_file_path}, skipping") - continue - - logger.debug(f"Jest XML: processing testcase name={test_name}, classname={test_class_path}") - - # First, try direct lookup in instrumented file paths - # This handles cases where instrumented files are in temp directories - test_file_path = None - test_type = None - - if test_class_path: - # Try exact match with classname (which should be the filepath from jest-junit) - if test_class_path in instrumented_path_lookup: - test_file_path, test_type = instrumented_path_lookup[test_class_path] - else: - # Try resolving the path and matching - try: - resolved_path = str(Path(test_class_path).resolve()) - if resolved_path in instrumented_path_lookup: - test_file_path, test_type = instrumented_path_lookup[resolved_path] - except Exception: - pass - - # If direct lookup failed, try the file attribute - if test_file_path is None: - test_file_name = suite._elem.attrib.get("file") or testcase._elem.attrib.get("file") # noqa: SLF001 - if test_file_name: - if test_file_name in instrumented_path_lookup: - test_file_path, test_type = instrumented_path_lookup[test_file_name] - else: - try: - resolved_path = str(Path(test_file_name).resolve()) - if resolved_path in instrumented_path_lookup: - test_file_path, test_type = instrumented_path_lookup[resolved_path] - except Exception: - pass - - # Fall back to traditional path resolution if direct lookup failed - if test_file_path is None: - test_file_path = resolve_test_file_from_class_path(test_class_path, base_dir) - if test_file_path is None: - test_file_name = suite._elem.attrib.get("file") or testcase._elem.attrib.get("file") # noqa: SLF001 - if test_file_name: - test_file_path = base_dir.parent / test_file_name - if not test_file_path.exists(): - test_file_path = base_dir / test_file_name - - # For Jest tests in monorepos, test files may not exist after cleanup - # but we can still parse results and infer test type from the path - if test_file_path is None: - logger.warning(f"Could not resolve test file for Jest test: {test_class_path}") - continue - - # Get test type if not already set from lookup - if test_type is None and test_file_path.exists(): - test_type = test_files.get_test_type_by_instrumented_file_path(test_file_path) - if test_type is None: - # Infer test type from filename pattern - filename = test_file_path.name - if "__perf_test_" in filename or "_perf_test_" in filename: - test_type = TestType.GENERATED_PERFORMANCE - elif "__unit_test_" in filename or "_unit_test_" in filename: - test_type = TestType.GENERATED_REGRESSION - else: - # Default to GENERATED_REGRESSION for Jest tests - test_type = TestType.GENERATED_REGRESSION - - # For Jest tests, keep the relative file path with extension intact - # (Python uses module_name_from_file_path which strips extensions) - try: - test_module_path = str(test_file_path.relative_to(test_config.tests_project_rootdir)) - except ValueError: - test_module_path = test_file_path.name - result = testcase.is_passed - - # Check for timeout - timed_out = False - if len(testcase.result) >= 1: - message = (testcase.result[0].message or "").lower() - if "timeout" in message or "timed out" in message: - timed_out = True - - # Find matching timing markers for this test - # Jest test names in markers are sanitized by codeflash-jest-helper's sanitizeTestId() - # which replaces: !#: (space) ()[]{}|\/*?^$.+- with underscores - # IMPORTANT: Must match Jest helper's sanitization exactly for marker matching to work - # Pattern from capture.js: /[!#: ()\[\]{}|\\/*?^$.+\-]/g - sanitized_test_name = re.sub(r"[!#: ()\[\]{}|\\/*?^$.+\-]", "_", test_name) - matching_starts = [m for m in start_matches if sanitized_test_name in m.group(2)] - - # For performance tests (capturePerf), there are no START markers - only END markers with duration - # Check for END markers directly if no START markers found - matching_ends_direct = [] - if not matching_starts: - # Look for END markers that match this test (performance test format) - # END marker format: !######module:testName:funcName:loopIndex:invocationId:durationNs######! - for end_key, end_match in end_matches_dict.items(): - # end_key is (module, testName, funcName, loopIndex, invocationId) - if len(end_key) >= 2 and sanitized_test_name in end_key[1]: - matching_ends_direct.append(end_match) - - if not matching_starts and not matching_ends_direct: - # No timing markers found - add basic result - test_results.add( - FunctionTestInvocation( - loop_index=1, - id=InvocationId( - test_module_path=test_module_path, - test_class_name=None, - test_function_name=test_name, - function_getting_tested="", - iteration_id="", - ), - file_name=test_file_path, - runtime=None, - test_framework=test_config.test_framework, - did_pass=result, - test_type=test_type, - return_value=None, - timed_out=timed_out, - stdout="", - ) - ) - elif matching_ends_direct: - # Performance test format: process END markers directly (no START markers) - for end_match in matching_ends_direct: - groups = end_match.groups() - # groups: (module, testName, funcName, loopIndex, invocationId, durationNs) - func_name = groups[2] - loop_index = int(groups[3]) if groups[3].isdigit() else 1 - line_id = groups[4] - try: - runtime = int(groups[5]) - except (ValueError, IndexError): - runtime = None - test_results.add( - FunctionTestInvocation( - loop_index=loop_index, - id=InvocationId( - test_module_path=test_module_path, - test_class_name=None, - test_function_name=test_name, - function_getting_tested=func_name, - iteration_id=line_id, - ), - file_name=test_file_path, - runtime=runtime, - test_framework=test_config.test_framework, - did_pass=result, - test_type=test_type, - return_value=None, - timed_out=timed_out, - stdout="", - ) - ) - else: - # Process each timing marker - for match in matching_starts: - groups = match.groups() - # groups: (testName, testName2, funcName, loopIndex, lineId) - func_name = groups[2] - loop_index = int(groups[3]) if groups[3].isdigit() else 1 - line_id = groups[4] - - # Find matching end marker - end_key = groups[:5] - end_match = end_matches_dict.get(end_key) - - runtime = None - if end_match: - # Duration is in the 6th group (index 5) - with contextlib.suppress(ValueError, IndexError): - runtime = int(end_match.group(6)) - test_results.add( - FunctionTestInvocation( - loop_index=loop_index, - id=InvocationId( - test_module_path=test_module_path, - test_class_name=None, - test_function_name=test_name, - function_getting_tested=func_name, - iteration_id=line_id, - ), - file_name=test_file_path, - runtime=runtime, - test_framework=test_config.test_framework, - did_pass=result, - test_type=test_type, - return_value=None, - timed_out=timed_out, - stdout="", - ) - ) - - if not test_results: - logger.info( - f"No Jest test results parsed from {test_xml_file_path} " - f"(found {suite_count} suites, {testcase_count} testcases)" - ) - if run_result is not None: - logger.debug(f"Jest stdout: {run_result.stdout[:1000] if run_result.stdout else 'empty'}") - else: - logger.debug( - f"Jest XML parsing complete: {len(test_results.test_results)} results " - f"from {suite_count} suites, {testcase_count} testcases" - ) - - return test_results - - def parse_test_xml( test_xml_file_path: Path, test_files: TestFiles, @@ -987,7 +636,14 @@ def parse_test_xml( ) -> TestResults: # Route to Jest-specific parser for JavaScript/TypeScript tests if is_javascript(): - return parse_jest_test_xml(test_xml_file_path, test_files, test_config, run_result) + return _parse_jest_test_xml( + test_xml_file_path, + test_files, + test_config, + run_result, + parse_func=parse_func, + resolve_test_file_from_class_path=resolve_test_file_from_class_path, + ) test_results = TestResults() # Parse unittest output @@ -1500,7 +1156,7 @@ def parse_test_results( code_context: CodeOptimizationContext | None = None, run_result: subprocess.CompletedProcess | None = None, skip_sqlite_cleanup: bool = False, - testing_type: TestingMode = TestingMode.BEHAVIOR + testing_type: TestingMode = TestingMode.BEHAVIOR, ) -> tuple[TestResults, CoverageData | None]: test_results_xml = parse_test_xml( test_xml_path, test_files=test_files, test_config=test_config, run_result=run_result diff --git a/codeflash/verification/test_runner.py b/codeflash/verification/test_runner.py index e622ea067..05b28a68a 100644 --- a/codeflash/verification/test_runner.py +++ b/codeflash/verification/test_runner.py @@ -351,6 +351,7 @@ def run_benchmarking_tests( pytest_max_loops: int = 100_000, js_project_root: Path | None = None, ) -> tuple[Path, subprocess.CompletedProcess]: + logger.debug(f"run_benchmarking_tests called: framework={test_framework}, num_files={len(test_paths.test_files)}") # Check if there's a language support for this test framework that implements run_benchmarking_tests language_support = get_language_support_by_framework(test_framework) if language_support is not None and hasattr(language_support, "run_benchmarking_tests"): diff --git a/codeflash/verification/verification_utils.py b/codeflash/verification/verification_utils.py index 45b96ff51..857a5f9fe 100644 --- a/codeflash/verification/verification_utils.py +++ b/codeflash/verification/verification_utils.py @@ -16,6 +16,7 @@ def get_test_file_path( test_type: str = "unit", package_name: str | None = None, class_name: str | None = None, + source_file_path: Path | None = None, ) -> Path: assert test_type in {"unit", "inspired", "replay", "perf"} function_name_safe = function_name.replace(".", "_") @@ -41,13 +42,82 @@ def get_test_file_path( # Create package directory if needed path.parent.mkdir(parents=True, exist_ok=True) else: + # For JavaScript/TypeScript, place generated tests in a subdirectory that matches + # Vitest/Jest include patterns (e.g., test/**/*.test.ts) + if is_javascript(): + package_test_dir = _find_js_package_test_dir(test_dir, source_file_path) + if package_test_dir: + test_dir = package_test_dir + path = test_dir / f"test_{function_name_safe}__{test_type}_test_{iteration}{extension}" if path.exists(): - return get_test_file_path(test_dir, function_name, iteration + 1, test_type, package_name, class_name) + return get_test_file_path( + test_dir, function_name, iteration + 1, test_type, package_name, class_name, source_file_path + ) return path +def _find_js_package_test_dir(tests_root: Path, source_file_path: Path | None) -> Path | None: + """Find the appropriate test directory for a JavaScript/TypeScript package. + + For monorepos, this finds the package's test directory from the source file path. + For example: packages/workflow/src/utils.ts -> packages/workflow/test/codeflash-generated/ + + Args: + tests_root: The root tests directory (may be monorepo packages root). + source_file_path: Path to the source file being tested. + + Returns: + The test directory path, or None if not found. + + """ + if source_file_path is None: + # No source path provided, check if test_dir itself has a test subdirectory + for test_subdir_name in ["test", "tests", "__tests__", "src/__tests__"]: + test_subdir = tests_root / test_subdir_name + if test_subdir.is_dir(): + codeflash_test_dir = test_subdir / "codeflash-generated" + codeflash_test_dir.mkdir(parents=True, exist_ok=True) + return codeflash_test_dir + return None + + try: + # Resolve paths for reliable comparison + tests_root = tests_root.resolve() + source_path = Path(source_file_path).resolve() + + # Walk up from the source file to find a directory with package.json or test/ folder + package_dir = None + + for parent in source_path.parents: + # Stop if we've gone above or reached the tests_root level + # For monorepos, tests_root might be /packages/ and we want to search within packages + if parent in (tests_root, tests_root.parent): + break + + # Check if this looks like a package root + has_package_json = (parent / "package.json").exists() + has_test_dir = any((parent / d).is_dir() for d in ["test", "tests", "__tests__"]) + + if has_package_json or has_test_dir: + package_dir = parent + break + + if package_dir: + # Find the test directory in this package + for test_subdir_name in ["test", "tests", "__tests__", "src/__tests__"]: + test_subdir = package_dir / test_subdir_name + if test_subdir.is_dir(): + codeflash_test_dir = test_subdir / "codeflash-generated" + codeflash_test_dir.mkdir(parents=True, exist_ok=True) + return codeflash_test_dir + + return None + except Exception: + return None + + def delete_multiple_if_name_main(test_ast: ast.Module) -> ast.Module: if_indexes = [] for index, node in enumerate(test_ast.body): diff --git a/codeflash/verification/verifier.py b/codeflash/verification/verifier.py index b677d1819..9a48edb0b 100644 --- a/codeflash/verification/verifier.py +++ b/codeflash/verification/verifier.py @@ -66,6 +66,7 @@ def generate_tests( if is_javascript(): from codeflash.languages.javascript.instrument import ( TestingMode, + fix_import_path_for_test_location, instrument_generated_js_test, validate_and_fix_import_style, ) @@ -76,6 +77,12 @@ def generate_tests( source_file = Path(function_to_optimize.file_path) + # Fix import paths to be relative to test file location + # AI may generate imports like 'apps/web/app/file' instead of '../../app/file' + generated_test_source = fix_import_path_for_test_location( + generated_test_source, source_file, test_path, module_path + ) + # Validate and fix import styles (default vs named exports) generated_test_source = validate_and_fix_import_style( generated_test_source, source_file, function_to_optimize.function_name diff --git a/codeflash/version.py b/codeflash/version.py index 67379ab0c..6d60ab0c2 100644 --- a/codeflash/version.py +++ b/codeflash/version.py @@ -1,2 +1,2 @@ # These version placeholders will be replaced by uv-dynamic-versioning during build. -__version__ = "0.20.0.post414.dev0+2ad731d3" +__version__ = "0.20.0.post510.dev0+b8932209" diff --git a/packages/codeflash/package-lock.json b/packages/codeflash/package-lock.json index c9febd5ab..aaffaef6a 100644 --- a/packages/codeflash/package-lock.json +++ b/packages/codeflash/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeflash", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeflash", - "version": "0.7.0", + "version": "0.8.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/codeflash/package.json b/packages/codeflash/package.json index a45c53e77..dfd3abdf1 100644 --- a/packages/codeflash/package.json +++ b/packages/codeflash/package.json @@ -1,6 +1,6 @@ { "name": "codeflash", - "version": "0.7.0", + "version": "0.8.0", "description": "Codeflash - AI-powered code optimization for JavaScript and TypeScript", "main": "runtime/index.js", "types": "runtime/index.d.ts", diff --git a/packages/codeflash/runtime/capture.js b/packages/codeflash/runtime/capture.js index eabcee539..616e2907c 100644 --- a/packages/codeflash/runtime/capture.js +++ b/packages/codeflash/runtime/capture.js @@ -839,7 +839,7 @@ function setTestName(name) { resetInvocationCounters(); } -// Jest lifecycle hooks - these run automatically when this module is imported +// Jest/Vitest lifecycle hooks - these run automatically when this module is imported if (typeof beforeEach !== 'undefined') { beforeEach(() => { // Get current test name and path from Jest's expect state @@ -854,6 +854,17 @@ if (typeof beforeEach !== 'undefined') { } // Reset invocation counters for each test resetInvocationCounters(); + + // For Vitest (no external loop-runner), reset perf state for each test + // so each test gets its own time budget for internal looping. + // For Jest with loop-runner, CODEFLASH_PERF_CURRENT_BATCH is set, + // and we want shared state across the test file. + const hasExternalLoopRunner = process.env.CODEFLASH_PERF_CURRENT_BATCH !== undefined; + if (!hasExternalLoopRunner) { + resetPerfState(); + // Also reset invocation loop counts so each test starts fresh + sharedPerfState.invocationLoopCounts = {}; + } }); } diff --git a/packages/codeflash/runtime/index.js b/packages/codeflash/runtime/index.js index e7ecb158c..982912c24 100644 --- a/packages/codeflash/runtime/index.js +++ b/packages/codeflash/runtime/index.js @@ -77,8 +77,13 @@ module.exports = { incrementBatch: capture.incrementBatch, getCurrentBatch: capture.getCurrentBatch, checkSharedTimeLimit: capture.checkSharedTimeLimit, - PERF_BATCH_SIZE: capture.PERF_BATCH_SIZE, - PERF_LOOP_COUNT: capture.PERF_LOOP_COUNT, + // Getter functions for dynamic env var reading (not constants) + getPerfBatchSize: capture.getPerfBatchSize, + getPerfLoopCount: capture.getPerfLoopCount, + getPerfMinLoops: capture.getPerfMinLoops, + getPerfTargetDurationMs: capture.getPerfTargetDurationMs, + getPerfStabilityCheck: capture.getPerfStabilityCheck, + getPerfCurrentBatch: capture.getPerfCurrentBatch, // === Feature Detection === hasV8: serializer.hasV8, diff --git a/packages/codeflash/scripts/postinstall.js b/packages/codeflash/scripts/postinstall.js index 4dafbd713..e3c5820f9 100644 --- a/packages/codeflash/scripts/postinstall.js +++ b/packages/codeflash/scripts/postinstall.js @@ -106,40 +106,63 @@ function installUv() { } } +/** + * Check if git is available + */ +function hasGit() { + try { + const result = spawnSync('git', ['--version'], { + stdio: 'ignore', + shell: true, + }); + return result.status === 0; + } catch { + return false; + } +} + /** * Install codeflash Python CLI using uv tool + * + * Installation priority: + * 1. GitHub main branch (if git available) - gets latest features + * 2. PyPI (fallback) - stable release + * + * We prefer GitHub because it has the latest JS/TS support that may not + * be published to PyPI yet. uv handles cloning internally in its cache. */ function installCodeflash(uvBin) { logStep('2/3', 'Installing codeflash Python CLI...'); + const GITHUB_REPO = 'git+https://github.com/codeflash-ai/codeflash.git'; + + // Priority 1: Install from GitHub (latest features, requires git) + if (hasGit()) { + try { + execSync(`"${uvBin}" tool install --force --python python3.12 "${GITHUB_REPO}"`, { + stdio: 'inherit', + shell: true, + }); + logSuccess('codeflash CLI installed from GitHub (latest)'); + return true; + } catch (error) { + logWarning(`GitHub installation failed: ${error.message}`); + logWarning('Falling back to PyPI...'); + } + } else { + logWarning('Git not found, installing from PyPI...'); + } + + // Priority 2: Install from PyPI (stable release fallback) try { - // Use uv tool install to install codeflash in an isolated environment - // This avoids conflicts with any existing Python environments execSync(`"${uvBin}" tool install --force --python python3.12 codeflash`, { stdio: 'inherit', shell: true, }); - logSuccess('codeflash CLI installed successfully'); + logSuccess('codeflash CLI installed from PyPI'); return true; } catch (error) { - // If codeflash is not on PyPI yet, try installing from the local package - logWarning('codeflash not found on PyPI, trying local installation...'); - try { - // Try installing from the current codeflash repo if we're in development - const cliRoot = path.resolve(__dirname, '..', '..', '..'); - const pyprojectPath = path.join(cliRoot, 'pyproject.toml'); - - if (fs.existsSync(pyprojectPath)) { - execSync(`"${uvBin}" tool install --force "${cliRoot}"`, { - stdio: 'inherit', - shell: true, - }); - logSuccess('codeflash CLI installed from local source'); - return true; - } - } catch (localError) { - logError(`Failed to install codeflash: ${localError.message}`); - } + logError(`Failed to install codeflash: ${error.message}`); return false; } } diff --git a/pyproject.toml b/pyproject.toml index d4258dda8..f02995db3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "pygls>=2.0.0,<3.0.0", "codeflash-benchmark", "filelock", - "pytest-asyncio>=1.2.0", + "pytest-asyncio>=0.18.0", ] [project.urls] @@ -88,6 +88,7 @@ tests = [ "jax>=0.4.30", "numpy>=2.0.2", "pandas>=2.3.3", + "pyarrow>=15.0.0", "pyrsistent>=0.20.0", "scipy>=1.13.1", "torch>=2.8.0", diff --git a/tests/code_utils/__init__.py b/tests/code_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/code_utils/test_normalize_ignore_paths.py b/tests/code_utils/test_normalize_ignore_paths.py new file mode 100644 index 000000000..0b0bbe41c --- /dev/null +++ b/tests/code_utils/test_normalize_ignore_paths.py @@ -0,0 +1,209 @@ +"""Tests for normalize_ignore_paths function.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from codeflash.code_utils.code_utils import is_glob_pattern, normalize_ignore_paths + + +class TestIsGlobPattern: + """Tests for is_glob_pattern function.""" + + def test_asterisk_pattern(self) -> None: + assert is_glob_pattern("*.py") is True + assert is_glob_pattern("**/*.js") is True + assert is_glob_pattern("node_modules/*") is True + + def test_question_mark_pattern(self) -> None: + assert is_glob_pattern("file?.txt") is True + assert is_glob_pattern("test_?.py") is True + + def test_bracket_pattern(self) -> None: + assert is_glob_pattern("[abc].txt") is True + assert is_glob_pattern("file[0-9].log") is True + + def test_literal_paths(self) -> None: + assert is_glob_pattern("node_modules") is False + assert is_glob_pattern("src/utils") is False + assert is_glob_pattern("/absolute/path") is False + assert is_glob_pattern("relative/path/file.py") is False + + +class TestNormalizeIgnorePaths: + """Tests for normalize_ignore_paths function.""" + + def test_empty_list(self) -> None: + result = normalize_ignore_paths([]) + assert result == [] + + def test_literal_existing_path(self, tmp_path: Path) -> None: + # Create a directory + test_dir = tmp_path / "node_modules" + test_dir.mkdir() + + result = normalize_ignore_paths(["node_modules"], base_path=tmp_path) + + assert len(result) == 1 + assert result[0] == test_dir.resolve() + + def test_literal_nonexistent_path_skipped(self, tmp_path: Path) -> None: + # Don't create the directory - should be silently skipped + result = normalize_ignore_paths(["nonexistent_dir"], base_path=tmp_path) + + assert result == [] + + def test_multiple_literal_paths(self, tmp_path: Path) -> None: + # Create directories + dir1 = tmp_path / "node_modules" + dir2 = tmp_path / "dist" + dir1.mkdir() + dir2.mkdir() + + result = normalize_ignore_paths(["node_modules", "dist"], base_path=tmp_path) + + assert len(result) == 2 + assert set(result) == {dir1.resolve(), dir2.resolve()} + + def test_glob_pattern_single_asterisk(self, tmp_path: Path) -> None: + # Create test files + (tmp_path / "file1.log").touch() + (tmp_path / "file2.log").touch() + (tmp_path / "file.txt").touch() + + result = normalize_ignore_paths(["*.log"], base_path=tmp_path) + + assert len(result) == 2 + resolved_names = {p.name for p in result} + assert resolved_names == {"file1.log", "file2.log"} + + def test_glob_pattern_double_asterisk(self, tmp_path: Path) -> None: + # Create nested structure + subdir = tmp_path / "src" / "utils" + subdir.mkdir(parents=True) + (subdir / "test_helper.py").touch() + (tmp_path / "src" / "test_main.py").touch() + (tmp_path / "test_root.py").touch() + + result = normalize_ignore_paths(["**/test_*.py"], base_path=tmp_path) + + assert len(result) == 3 + resolved_names = {p.name for p in result} + assert resolved_names == {"test_helper.py", "test_main.py", "test_root.py"} + + def test_glob_pattern_directory_contents(self, tmp_path: Path) -> None: + # Create directory with contents + node_modules = tmp_path / "node_modules" + node_modules.mkdir() + (node_modules / "package1").mkdir() + (node_modules / "package2").mkdir() + + result = normalize_ignore_paths(["node_modules/*"], base_path=tmp_path) + + assert len(result) == 2 + resolved_names = {p.name for p in result} + assert resolved_names == {"package1", "package2"} + + def test_glob_pattern_no_matches(self, tmp_path: Path) -> None: + # Pattern with no matches should return empty list + result = normalize_ignore_paths(["*.nonexistent"], base_path=tmp_path) + + assert result == [] + + def test_mixed_literal_and_patterns(self, tmp_path: Path) -> None: + # Create test structure + node_modules = tmp_path / "node_modules" + node_modules.mkdir() + (tmp_path / "debug.log").touch() + (tmp_path / "error.log").touch() + + result = normalize_ignore_paths(["node_modules", "*.log"], base_path=tmp_path) + + assert len(result) == 3 + resolved_names = {p.name for p in result} + assert resolved_names == {"node_modules", "debug.log", "error.log"} + + def test_deduplication(self, tmp_path: Path) -> None: + # Create a file that matches multiple patterns + (tmp_path / "test.log").touch() + + # Same file should only appear once + result = normalize_ignore_paths(["test.log", "*.log"], base_path=tmp_path) + + assert len(result) == 1 + assert result[0].name == "test.log" + + def test_nested_directory_pattern(self, tmp_path: Path) -> None: + # Create nested test directories + tests_dir = tmp_path / "src" / "__tests__" + tests_dir.mkdir(parents=True) + (tests_dir / "test1.js").touch() + (tests_dir / "test2.js").touch() + + result = normalize_ignore_paths(["src/__tests__/*.js"], base_path=tmp_path) + + assert len(result) == 2 + resolved_names = {p.name for p in result} + assert resolved_names == {"test1.js", "test2.js"} + + def test_absolute_path_literal(self, tmp_path: Path) -> None: + # Create a directory + test_dir = tmp_path / "absolute_test" + test_dir.mkdir() + + # Use absolute path + result = normalize_ignore_paths([str(test_dir)], base_path=tmp_path) + + assert len(result) == 1 + assert result[0] == test_dir.resolve() + + def test_relative_path_with_subdirectory(self, tmp_path: Path) -> None: + # Create nested directory + nested = tmp_path / "src" / "vendor" + nested.mkdir(parents=True) + + result = normalize_ignore_paths(["src/vendor"], base_path=tmp_path) + + assert len(result) == 1 + assert result[0] == nested.resolve() + + def test_default_base_path_uses_cwd(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + # Change to tmp_path + monkeypatch.chdir(tmp_path) + + # Create a directory + test_dir = tmp_path / "test_dir" + test_dir.mkdir() + + # Call without base_path + result = normalize_ignore_paths(["test_dir"]) + + assert len(result) == 1 + assert result[0] == test_dir.resolve() + + def test_bracket_pattern(self, tmp_path: Path) -> None: + # Create files matching bracket pattern + (tmp_path / "file1.txt").touch() + (tmp_path / "file2.txt").touch() + (tmp_path / "file3.txt").touch() + (tmp_path / "fileA.txt").touch() + + result = normalize_ignore_paths(["file[12].txt"], base_path=tmp_path) + + assert len(result) == 2 + resolved_names = {p.name for p in result} + assert resolved_names == {"file1.txt", "file2.txt"} + + def test_question_mark_pattern(self, tmp_path: Path) -> None: + # Create files matching question mark pattern + (tmp_path / "test_a.py").touch() + (tmp_path / "test_b.py").touch() + (tmp_path / "test_ab.py").touch() + + result = normalize_ignore_paths(["test_?.py"], base_path=tmp_path) + + assert len(result) == 2 + resolved_names = {p.name for p in result} + assert resolved_names == {"test_a.py", "test_b.py"} diff --git a/tests/languages/javascript/test_support_dispatch.py b/tests/languages/javascript/test_support_dispatch.py index c3c703b97..46f08e913 100644 --- a/tests/languages/javascript/test_support_dispatch.py +++ b/tests/languages/javascript/test_support_dispatch.py @@ -182,7 +182,9 @@ def test_passes_loop_parameters(self, mock_vitest_runner: MagicMock, js_support: call_kwargs = mock_vitest_runner.call_args.kwargs assert call_kwargs["min_loops"] == 10 - assert call_kwargs["max_loops"] == 50 + # JS/TS always uses high max_loops (100_000) regardless of passed value + # Actual loop count is limited by target_duration, not max_loops + assert call_kwargs["max_loops"] == 100_000 assert call_kwargs["target_duration_ms"] == 5000 diff --git a/tests/languages/javascript/test_vitest_junit.py b/tests/languages/javascript/test_vitest_junit.py index 28a24ba08..ac52ffe3e 100644 --- a/tests/languages/javascript/test_vitest_junit.py +++ b/tests/languages/javascript/test_vitest_junit.py @@ -245,3 +245,217 @@ def test_timing_marker_with_special_characters_in_test_name(self) -> None: assert len(matches) == 1 assert matches[0][1] == "handles_n=0_correctly" + + +class TestFilenameBasedLookupFallback: + """Tests for filename-based lookup fallback in Jest/Vitest XML parsing. + + When JUnit XML has relative paths that can't be resolved to absolute paths + (because they're relative to Jest's CWD, not the parse-time CWD), the parser + should fall back to matching by filename only. + """ + + def test_filename_lookup_matches_relative_path(self) -> None: + """Should match test file by filename when classname has unresolvable relative path.""" + from unittest.mock import MagicMock + + from codeflash.languages.javascript.parse import parse_jest_test_xml + from codeflash.models.models import TestFile, TestFiles, TestType + + # Create a temporary XML file with a relative path that won't resolve + xml_content = """ + + + + +""" + + with tempfile.NamedTemporaryFile(suffix=".xml", mode="w", delete=False) as f: + f.write(xml_content) + f.flush() + junit_file = Path(f.name) + + # Create a mock test file with an absolute instrumented path + # The filename should match even though the full path differs + with tempfile.TemporaryDirectory() as tmpdir: + instrumented_path = Path(tmpdir) / "utils__perfinstrumented.test.ts" + instrumented_path.touch() + + test_file = TestFile( + original_file_path=Path(tmpdir) / "utils.test.ts", + test_type=TestType.GENERATED_REGRESSION, + instrumented_behavior_file_path=instrumented_path, + ) + test_files = TestFiles(test_files=[test_file]) + + test_config = MagicMock() + test_config.tests_project_rootdir = Path(tmpdir) + test_config.test_framework = "jest" + + # Parse the XML - should use filename fallback + results = parse_jest_test_xml( + junit_file, + test_files, + test_config, + parse_func=None, # Will use default + resolve_test_file_from_class_path=lambda x, y: None, # Force fallback + ) + + # Should have found 1 test result via filename matching + assert len(results.test_results) == 1 + assert results.test_results[0].file_name == instrumented_path + assert results.test_results[0].test_type == TestType.GENERATED_REGRESSION + + def test_filename_lookup_with_duplicate_filenames_uses_first(self) -> None: + """When multiple test files have same filename, use the first one registered.""" + from unittest.mock import MagicMock + + from codeflash.languages.javascript.parse import parse_jest_test_xml + from codeflash.models.models import TestFile, TestFiles, TestType + + xml_content = """ + + + + +""" + + with tempfile.NamedTemporaryFile(suffix=".xml", mode="w", delete=False) as f: + f.write(xml_content) + f.flush() + junit_file = Path(f.name) + + with tempfile.TemporaryDirectory() as tmpdir: + # Create two test files with the same filename in different directories + dir1 = Path(tmpdir) / "pkg1" + dir2 = Path(tmpdir) / "pkg2" + dir1.mkdir() + dir2.mkdir() + + path1 = dir1 / "same_name.test.ts" + path2 = dir2 / "same_name.test.ts" + path1.touch() + path2.touch() + + test_file1 = TestFile( + original_file_path=path1, + test_type=TestType.GENERATED_REGRESSION, + instrumented_behavior_file_path=path1, + ) + test_file2 = TestFile( + original_file_path=path2, + test_type=TestType.REPLAY_TEST, # Different type + instrumented_behavior_file_path=path2, + ) + # First file should win in filename lookup + test_files = TestFiles(test_files=[test_file1, test_file2]) + + test_config = MagicMock() + test_config.tests_project_rootdir = Path(tmpdir) + test_config.test_framework = "jest" + + results = parse_jest_test_xml( + junit_file, + test_files, + test_config, + parse_func=None, + resolve_test_file_from_class_path=lambda x, y: None, + ) + + assert len(results.test_results) == 1 + # Should use first registered file + assert results.test_results[0].file_name == path1 + assert results.test_results[0].test_type == TestType.GENERATED_REGRESSION + + def test_filename_lookup_extracts_filename_from_nested_path(self) -> None: + """Should extract filename correctly from deeply nested relative paths.""" + from unittest.mock import MagicMock + + from codeflash.languages.javascript.parse import parse_jest_test_xml + from codeflash.models.models import TestFile, TestFiles, TestType + + xml_content = """ + + + + +""" + + with tempfile.NamedTemporaryFile(suffix=".xml", mode="w", delete=False) as f: + f.write(xml_content) + f.flush() + junit_file = Path(f.name) + + with tempfile.TemporaryDirectory() as tmpdir: + instrumented_path = Path(tmpdir) / "utils__perfinstrumented.test.ts" + instrumented_path.touch() + + test_file = TestFile( + original_file_path=Path(tmpdir) / "utils.test.ts", + test_type=TestType.GENERATED_REGRESSION, + instrumented_behavior_file_path=instrumented_path, + ) + test_files = TestFiles(test_files=[test_file]) + + test_config = MagicMock() + test_config.tests_project_rootdir = Path(tmpdir) + test_config.test_framework = "jest" + + results = parse_jest_test_xml( + junit_file, + test_files, + test_config, + parse_func=None, + resolve_test_file_from_class_path=lambda x, y: None, + ) + + # Should match despite deeply nested path in XML + assert len(results.test_results) == 1 + assert results.test_results[0].file_name == instrumented_path + + def test_no_match_when_filename_not_in_lookup(self) -> None: + """Should skip test case when filename doesn't match any registered test file.""" + from unittest.mock import MagicMock + + from codeflash.languages.javascript.parse import parse_jest_test_xml + from codeflash.models.models import TestFile, TestFiles, TestType + + # XML with a filename that doesn't match any registered test file + xml_content = """ + + + + +""" + + with tempfile.NamedTemporaryFile(suffix=".xml", mode="w", delete=False) as f: + f.write(xml_content) + f.flush() + junit_file = Path(f.name) + + with tempfile.TemporaryDirectory() as tmpdir: + # Register a test file with a DIFFERENT filename + instrumented_path = Path(tmpdir) / "different_file.test.ts" + instrumented_path.touch() + + test_file = TestFile( + original_file_path=Path(tmpdir) / "different.test.ts", + test_type=TestType.GENERATED_REGRESSION, + instrumented_behavior_file_path=instrumented_path, + ) + test_files = TestFiles(test_files=[test_file]) + + test_config = MagicMock() + test_config.tests_project_rootdir = Path(tmpdir) + test_config.test_framework = "jest" + + results = parse_jest_test_xml( + junit_file, + test_files, + test_config, + parse_func=None, + resolve_test_file_from_class_path=lambda x, y: None, + ) + + # Should have no results since filename doesn't match + assert len(results.test_results) == 0 diff --git a/tests/languages/javascript/test_vitest_runner.py b/tests/languages/javascript/test_vitest_runner.py index 0bfe239d3..8dff99ef5 100644 --- a/tests/languages/javascript/test_vitest_runner.py +++ b/tests/languages/javascript/test_vitest_runner.py @@ -160,7 +160,8 @@ def test_includes_output_file_when_provided(self) -> None: cmd = _build_vitest_behavioral_command([test_file], timeout=60, output_file=output_file) - assert f"--outputFile={output_file}" in cmd + # Vitest requires dot notation for junit output when using multiple reporters + assert f"--outputFile.junit={output_file}" in cmd class TestBuildVitestBenchmarkingCommand: diff --git a/tests/test_comparator.py b/tests/test_comparator.py index 753929843..100e385fd 100644 --- a/tests/test_comparator.py +++ b/tests/test_comparator.py @@ -7,6 +7,7 @@ import re import sys import uuid +import weakref from collections import ChainMap, Counter, OrderedDict, UserDict, UserList, UserString, defaultdict, deque, namedtuple from enum import Enum, Flag, IntFlag, auto from pathlib import Path @@ -136,6 +137,162 @@ def test_basic_python_objects() -> None: assert not comparator(a, c) +def test_weakref() -> None: + """Test comparator for weakref.ref objects.""" + + # Helper class that supports weak references and has comparable __dict__ + class Holder: + def __init__(self, value): + self.value = value + + # Test weak references to the same object + obj = Holder([1, 2, 3]) + ref1 = weakref.ref(obj) + ref2 = weakref.ref(obj) + assert comparator(ref1, ref2) + + # Test weak references to equivalent but different objects + obj1 = Holder({"key": "value"}) + obj2 = Holder({"key": "value"}) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert comparator(ref1, ref2) + + # Test weak references to different objects + obj1 = Holder([1, 2, 3]) + obj2 = Holder([1, 2, 4]) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert not comparator(ref1, ref2) + + # Test weak references with different data + obj1 = Holder([1, 2, 3]) + obj2 = Holder([1, 2, 3, 4]) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert not comparator(ref1, ref2) + + # Test dead weak references (both dead) + obj1 = Holder([1, 2, 3]) + obj2 = Holder([1, 2, 3]) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + del obj1 + del obj2 + # Both refs are now dead, should be equal + assert comparator(ref1, ref2) + + # Test one dead, one alive weak reference + obj1 = Holder([1, 2, 3]) + obj2 = Holder([1, 2, 3]) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + del obj1 + # ref1 is dead, ref2 is alive, should not be equal + assert not comparator(ref1, ref2) + assert not comparator(ref2, ref1) + + # Test weak references to nested structures + obj1 = Holder({"nested": [1, 2, {"inner": "value"}]}) + obj2 = Holder({"nested": [1, 2, {"inner": "value"}]}) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert comparator(ref1, ref2) + + # Test weak references to nested structures with differences + obj1 = Holder({"nested": [1, 2, {"inner": "value1"}]}) + obj2 = Holder({"nested": [1, 2, {"inner": "value2"}]}) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert not comparator(ref1, ref2) + + # Test weak references in a dictionary (simulating __dict__ with weakrefs) + obj1 = Holder([1, 2, 3]) + obj2 = Holder([1, 2, 3]) + dict1 = {"data": 42, "ref": weakref.ref(obj1)} + dict2 = {"data": 42, "ref": weakref.ref(obj2)} + assert comparator(dict1, dict2) + + # Test weak references in a dictionary with different referents + obj1 = Holder([1, 2, 3]) + obj2 = Holder([4, 5, 6]) + dict1 = {"data": 42, "ref": weakref.ref(obj1)} + dict2 = {"data": 42, "ref": weakref.ref(obj2)} + assert not comparator(dict1, dict2) + + # Test weak references in a list + obj1 = Holder({"a": 1}) + obj2 = Holder({"a": 1}) + list1 = [weakref.ref(obj1), "other"] + list2 = [weakref.ref(obj2), "other"] + assert comparator(list1, list2) + + +def test_weakref_to_custom_objects() -> None: + """Test comparator for weakref.ref to custom class instances.""" + + class MyClass: + def __init__(self, value): + self.value = value + + # Test weak references to equivalent custom objects + obj1 = MyClass(42) + obj2 = MyClass(42) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert comparator(ref1, ref2) + + # Test weak references to different custom objects + obj1 = MyClass(42) + obj2 = MyClass(99) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert not comparator(ref1, ref2) + + # Test weak references to custom objects with nested data + class Container: + def __init__(self, items): + self.items = items + + obj1 = Container([1, 2, 3]) + obj2 = Container([1, 2, 3]) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert comparator(ref1, ref2) + + obj1 = Container([1, 2, 3]) + obj2 = Container([1, 2, 4]) + ref1 = weakref.ref(obj1) + ref2 = weakref.ref(obj2) + assert not comparator(ref1, ref2) + + +def test_weakref_with_callbacks() -> None: + """Test that weakrefs with callbacks are compared correctly.""" + + class Holder: + def __init__(self, value): + self.value = value + + callback_called = [] + + def callback(ref): + callback_called.append(ref) + + obj1 = Holder([1, 2, 3]) + obj2 = Holder([1, 2, 3]) + # Weakrefs with callbacks should still compare based on referents + ref1 = weakref.ref(obj1, callback) + ref2 = weakref.ref(obj2, callback) + assert comparator(ref1, ref2) + + obj1 = Holder([1, 2, 3]) + obj2 = Holder([4, 5, 6]) + ref1 = weakref.ref(obj1, callback) + ref2 = weakref.ref(obj2, callback) + assert not comparator(ref1, ref2) + + @pytest.mark.parametrize( "r1, r2, expected", [ @@ -645,6 +802,137 @@ def test_pandas(): assert comparator(filtered1, filtered2) +def test_pyarrow(): + try: + import pyarrow as pa + except ImportError: + pytest.skip() + + # Test PyArrow Table + table1 = pa.table({"a": [1, 2, 3], "b": [4, 5, 6]}) + table2 = pa.table({"a": [1, 2, 3], "b": [4, 5, 6]}) + table3 = pa.table({"a": [1, 2, 3], "b": [4, 5, 7]}) + table4 = pa.table({"a": [1, 2, 3, 4], "b": [4, 5, 6, 7]}) + table5 = pa.table({"a": [1, 2, 3], "c": [4, 5, 6]}) # different column name + + assert comparator(table1, table2) + assert not comparator(table1, table3) + assert not comparator(table1, table4) + assert not comparator(table1, table5) + + # Test PyArrow RecordBatch + batch1 = pa.RecordBatch.from_pydict({"x": [1, 2], "y": [3.0, 4.0]}) + batch2 = pa.RecordBatch.from_pydict({"x": [1, 2], "y": [3.0, 4.0]}) + batch3 = pa.RecordBatch.from_pydict({"x": [1, 2], "y": [3.0, 5.0]}) + batch4 = pa.RecordBatch.from_pydict({"x": [1, 2, 3], "y": [3.0, 4.0, 5.0]}) + + assert comparator(batch1, batch2) + assert not comparator(batch1, batch3) + assert not comparator(batch1, batch4) + + # Test PyArrow Array + arr1 = pa.array([1, 2, 3]) + arr2 = pa.array([1, 2, 3]) + arr3 = pa.array([1, 2, 4]) + arr4 = pa.array([1, 2, 3, 4]) + arr5 = pa.array([1.0, 2.0, 3.0]) # different type + + assert comparator(arr1, arr2) + assert not comparator(arr1, arr3) + assert not comparator(arr1, arr4) + assert not comparator(arr1, arr5) + + # Test PyArrow Array with nulls + arr_null1 = pa.array([1, None, 3]) + arr_null2 = pa.array([1, None, 3]) + arr_null3 = pa.array([1, 2, 3]) + + assert comparator(arr_null1, arr_null2) + assert not comparator(arr_null1, arr_null3) + + # Test PyArrow ChunkedArray + chunked1 = pa.chunked_array([[1, 2], [3, 4]]) + chunked2 = pa.chunked_array([[1, 2], [3, 4]]) + chunked3 = pa.chunked_array([[1, 2], [3, 5]]) + chunked4 = pa.chunked_array([[1, 2, 3], [4, 5]]) + + assert comparator(chunked1, chunked2) + assert not comparator(chunked1, chunked3) + assert not comparator(chunked1, chunked4) + + # Test PyArrow Scalar + scalar1 = pa.scalar(42) + scalar2 = pa.scalar(42) + scalar3 = pa.scalar(43) + scalar4 = pa.scalar(42.0) # different type + + assert comparator(scalar1, scalar2) + assert not comparator(scalar1, scalar3) + assert not comparator(scalar1, scalar4) + + # Test null scalars + null_scalar1 = pa.scalar(None, type=pa.int64()) + null_scalar2 = pa.scalar(None, type=pa.int64()) + null_scalar3 = pa.scalar(None, type=pa.float64()) + + assert comparator(null_scalar1, null_scalar2) + assert not comparator(null_scalar1, null_scalar3) + + # Test PyArrow Schema + schema1 = pa.schema([("a", pa.int64()), ("b", pa.float64())]) + schema2 = pa.schema([("a", pa.int64()), ("b", pa.float64())]) + schema3 = pa.schema([("a", pa.int64()), ("c", pa.float64())]) + schema4 = pa.schema([("a", pa.int32()), ("b", pa.float64())]) + + assert comparator(schema1, schema2) + assert not comparator(schema1, schema3) + assert not comparator(schema1, schema4) + + # Test PyArrow Field + field1 = pa.field("name", pa.int64()) + field2 = pa.field("name", pa.int64()) + field3 = pa.field("other", pa.int64()) + field4 = pa.field("name", pa.float64()) + + assert comparator(field1, field2) + assert not comparator(field1, field3) + assert not comparator(field1, field4) + + # Test PyArrow DataType + type1 = pa.int64() + type2 = pa.int64() + type3 = pa.int32() + type4 = pa.float64() + + assert comparator(type1, type2) + assert not comparator(type1, type3) + assert not comparator(type1, type4) + + # Test string arrays + str_arr1 = pa.array(["hello", "world"]) + str_arr2 = pa.array(["hello", "world"]) + str_arr3 = pa.array(["hello", "there"]) + + assert comparator(str_arr1, str_arr2) + assert not comparator(str_arr1, str_arr3) + + # Test nested types (struct) + struct_arr1 = pa.array([{"x": 1, "y": 2}, {"x": 3, "y": 4}]) + struct_arr2 = pa.array([{"x": 1, "y": 2}, {"x": 3, "y": 4}]) + struct_arr3 = pa.array([{"x": 1, "y": 2}, {"x": 3, "y": 5}]) + + assert comparator(struct_arr1, struct_arr2) + assert not comparator(struct_arr1, struct_arr3) + + # Test list arrays + list_arr1 = pa.array([[1, 2], [3, 4, 5]]) + list_arr2 = pa.array([[1, 2], [3, 4, 5]]) + list_arr3 = pa.array([[1, 2], [3, 4, 6]]) + + assert comparator(list_arr1, list_arr2) + assert not comparator(list_arr1, list_arr3) + + def test_pyrsistent(): try: from pyrsistent import PBag, PClass, PRecord, field, pdeque, pmap, pset, pvector # type: ignore @@ -877,6 +1165,957 @@ def test_torch_device(): assert not comparator(l, n) +def test_torch_nn_linear(): + """Test comparator for torch.nn.Linear modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical Linear layers + torch.manual_seed(42) + a = nn.Linear(10, 5) + torch.manual_seed(42) + b = nn.Linear(10, 5) + assert comparator(a, b) + + # Test Linear layers with different weights (different seeds) + torch.manual_seed(42) + c = nn.Linear(10, 5) + torch.manual_seed(123) + d = nn.Linear(10, 5) + assert not comparator(c, d) + + # Test Linear layers with different in_features + torch.manual_seed(42) + e = nn.Linear(10, 5) + torch.manual_seed(42) + f = nn.Linear(20, 5) + assert not comparator(e, f) + + # Test Linear layers with different out_features + torch.manual_seed(42) + g = nn.Linear(10, 5) + torch.manual_seed(42) + h = nn.Linear(10, 10) + assert not comparator(g, h) + + # Test Linear with and without bias + torch.manual_seed(42) + i = nn.Linear(10, 5, bias=True) + torch.manual_seed(42) + j = nn.Linear(10, 5, bias=False) + assert not comparator(i, j) + + # Test Linear layers in train vs eval mode + torch.manual_seed(42) + k = nn.Linear(10, 5) + k.train() + torch.manual_seed(42) + l = nn.Linear(10, 5) + l.eval() + assert not comparator(k, l) + + +def test_torch_nn_conv2d(): + """Test comparator for torch.nn.Conv2d modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical Conv2d layers + torch.manual_seed(42) + a = nn.Conv2d(3, 16, kernel_size=3) + torch.manual_seed(42) + b = nn.Conv2d(3, 16, kernel_size=3) + assert comparator(a, b) + + # Test Conv2d with different weights + torch.manual_seed(42) + c = nn.Conv2d(3, 16, kernel_size=3) + torch.manual_seed(123) + d = nn.Conv2d(3, 16, kernel_size=3) + assert not comparator(c, d) + + # Test Conv2d with different in_channels + torch.manual_seed(42) + e = nn.Conv2d(3, 16, kernel_size=3) + torch.manual_seed(42) + f = nn.Conv2d(1, 16, kernel_size=3) + assert not comparator(e, f) + + # Test Conv2d with different out_channels + torch.manual_seed(42) + g = nn.Conv2d(3, 16, kernel_size=3) + torch.manual_seed(42) + h = nn.Conv2d(3, 32, kernel_size=3) + assert not comparator(g, h) + + # Test Conv2d with different kernel_size + torch.manual_seed(42) + i = nn.Conv2d(3, 16, kernel_size=3) + torch.manual_seed(42) + j = nn.Conv2d(3, 16, kernel_size=5) + assert not comparator(i, j) + + # Test Conv2d with different stride + torch.manual_seed(42) + k = nn.Conv2d(3, 16, kernel_size=3, stride=1) + torch.manual_seed(42) + l = nn.Conv2d(3, 16, kernel_size=3, stride=2) + assert not comparator(k, l) + + # Test Conv2d with different padding + torch.manual_seed(42) + m = nn.Conv2d(3, 16, kernel_size=3, padding=0) + torch.manual_seed(42) + n = nn.Conv2d(3, 16, kernel_size=3, padding=1) + assert not comparator(m, n) + + +def test_torch_nn_batchnorm(): + """Test comparator for torch.nn.BatchNorm modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical BatchNorm2d layers + torch.manual_seed(42) + a = nn.BatchNorm2d(16) + torch.manual_seed(42) + b = nn.BatchNorm2d(16) + assert comparator(a, b) + + # Test BatchNorm2d with different num_features + torch.manual_seed(42) + c = nn.BatchNorm2d(16) + torch.manual_seed(42) + d = nn.BatchNorm2d(32) + assert not comparator(c, d) + + # Test BatchNorm2d with different eps + torch.manual_seed(42) + e = nn.BatchNorm2d(16, eps=1e-5) + torch.manual_seed(42) + f = nn.BatchNorm2d(16, eps=1e-3) + assert not comparator(e, f) + + # Test BatchNorm2d with different momentum + torch.manual_seed(42) + g = nn.BatchNorm2d(16, momentum=0.1) + torch.manual_seed(42) + h = nn.BatchNorm2d(16, momentum=0.01) + assert not comparator(g, h) + + # Test BatchNorm2d with and without affine + torch.manual_seed(42) + i = nn.BatchNorm2d(16, affine=True) + torch.manual_seed(42) + j = nn.BatchNorm2d(16, affine=False) + assert not comparator(i, j) + + # Test BatchNorm2d running stats after forward passes + torch.manual_seed(42) + k = nn.BatchNorm2d(16) + k.train() + input_k = torch.randn(4, 16, 8, 8) + _ = k(input_k) + torch.manual_seed(42) + l = nn.BatchNorm2d(16) + l.train() + input_l = torch.randn(4, 16, 8, 8) + _ = l(input_l) + # Same seed means same running stats + assert comparator(k, l) + + # Test BatchNorm2d with different running stats + torch.manual_seed(42) + m = nn.BatchNorm2d(16) + m.train() + torch.manual_seed(42) + _ = m(torch.randn(4, 16, 8, 8)) + torch.manual_seed(42) + n = nn.BatchNorm2d(16) + n.train() + torch.manual_seed(123) + _ = n(torch.randn(4, 16, 8, 8)) + assert not comparator(m, n) + + # Test BatchNorm1d + torch.manual_seed(42) + o = nn.BatchNorm1d(16) + torch.manual_seed(42) + p = nn.BatchNorm1d(16) + assert comparator(o, p) + + +def test_torch_nn_dropout(): + """Test comparator for torch.nn.Dropout modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical Dropout layers + a = nn.Dropout(p=0.5) + b = nn.Dropout(p=0.5) + assert comparator(a, b) + + # Test Dropout with different p values + c = nn.Dropout(p=0.5) + d = nn.Dropout(p=0.3) + assert not comparator(c, d) + + # Test Dropout with different inplace values + e = nn.Dropout(p=0.5, inplace=False) + f = nn.Dropout(p=0.5, inplace=True) + assert not comparator(e, f) + + # Test Dropout2d + g = nn.Dropout2d(p=0.5) + h = nn.Dropout2d(p=0.5) + assert comparator(g, h) + + # Test Dropout vs Dropout2d (different types) + i = nn.Dropout(p=0.5) + j = nn.Dropout2d(p=0.5) + assert not comparator(i, j) + + +def test_torch_nn_activation(): + """Test comparator for torch.nn activation modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test ReLU + a = nn.ReLU() + b = nn.ReLU() + assert comparator(a, b) + + # Test ReLU with different inplace + c = nn.ReLU(inplace=False) + d = nn.ReLU(inplace=True) + assert not comparator(c, d) + + # Test LeakyReLU + e = nn.LeakyReLU(negative_slope=0.01) + f = nn.LeakyReLU(negative_slope=0.01) + assert comparator(e, f) + + # Test LeakyReLU with different negative_slope + g = nn.LeakyReLU(negative_slope=0.01) + h = nn.LeakyReLU(negative_slope=0.1) + assert not comparator(g, h) + + # Test Sigmoid vs ReLU (different types) + i = nn.Sigmoid() + j = nn.ReLU() + assert not comparator(i, j) + + # Test GELU + k = nn.GELU() + l = nn.GELU() + assert comparator(k, l) + + # Test Softmax + m = nn.Softmax(dim=1) + n = nn.Softmax(dim=1) + assert comparator(m, n) + + # Test Softmax with different dim + o = nn.Softmax(dim=1) + p = nn.Softmax(dim=0) + assert not comparator(o, p) + + +def test_torch_nn_pooling(): + """Test comparator for torch.nn pooling modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test MaxPool2d + a = nn.MaxPool2d(kernel_size=2) + b = nn.MaxPool2d(kernel_size=2) + assert comparator(a, b) + + # Test MaxPool2d with different kernel_size + c = nn.MaxPool2d(kernel_size=2) + d = nn.MaxPool2d(kernel_size=3) + assert not comparator(c, d) + + # Test MaxPool2d with different stride + e = nn.MaxPool2d(kernel_size=2, stride=2) + f = nn.MaxPool2d(kernel_size=2, stride=1) + assert not comparator(e, f) + + # Test AvgPool2d + g = nn.AvgPool2d(kernel_size=2) + h = nn.AvgPool2d(kernel_size=2) + assert comparator(g, h) + + # Test MaxPool2d vs AvgPool2d (different types) + i = nn.MaxPool2d(kernel_size=2) + j = nn.AvgPool2d(kernel_size=2) + assert not comparator(i, j) + + # Test AdaptiveAvgPool2d + k = nn.AdaptiveAvgPool2d(output_size=(1, 1)) + l = nn.AdaptiveAvgPool2d(output_size=(1, 1)) + assert comparator(k, l) + + # Test AdaptiveAvgPool2d with different output_size + m = nn.AdaptiveAvgPool2d(output_size=(1, 1)) + n = nn.AdaptiveAvgPool2d(output_size=(2, 2)) + assert not comparator(m, n) + + +def test_torch_nn_embedding(): + """Test comparator for torch.nn.Embedding modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical Embedding layers + torch.manual_seed(42) + a = nn.Embedding(1000, 128) + torch.manual_seed(42) + b = nn.Embedding(1000, 128) + assert comparator(a, b) + + # Test Embedding with different weights + torch.manual_seed(42) + c = nn.Embedding(1000, 128) + torch.manual_seed(123) + d = nn.Embedding(1000, 128) + assert not comparator(c, d) + + # Test Embedding with different num_embeddings + torch.manual_seed(42) + e = nn.Embedding(1000, 128) + torch.manual_seed(42) + f = nn.Embedding(2000, 128) + assert not comparator(e, f) + + # Test Embedding with different embedding_dim + torch.manual_seed(42) + g = nn.Embedding(1000, 128) + torch.manual_seed(42) + h = nn.Embedding(1000, 256) + assert not comparator(g, h) + + # Test Embedding with different padding_idx + torch.manual_seed(42) + i = nn.Embedding(1000, 128, padding_idx=0) + torch.manual_seed(42) + j = nn.Embedding(1000, 128, padding_idx=1) + assert not comparator(i, j) + + # Test Embedding with and without padding_idx + torch.manual_seed(42) + k = nn.Embedding(1000, 128) + torch.manual_seed(42) + l = nn.Embedding(1000, 128, padding_idx=0) + assert not comparator(k, l) + + +def test_torch_nn_lstm(): + """Test comparator for torch.nn.LSTM modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical LSTM layers + torch.manual_seed(42) + a = nn.LSTM(input_size=10, hidden_size=20, num_layers=2) + torch.manual_seed(42) + b = nn.LSTM(input_size=10, hidden_size=20, num_layers=2) + assert comparator(a, b) + + # Test LSTM with different weights + torch.manual_seed(42) + c = nn.LSTM(input_size=10, hidden_size=20, num_layers=2) + torch.manual_seed(123) + d = nn.LSTM(input_size=10, hidden_size=20, num_layers=2) + assert not comparator(c, d) + + # Test LSTM with different input_size + torch.manual_seed(42) + e = nn.LSTM(input_size=10, hidden_size=20) + torch.manual_seed(42) + f = nn.LSTM(input_size=20, hidden_size=20) + assert not comparator(e, f) + + # Test LSTM with different hidden_size + torch.manual_seed(42) + g = nn.LSTM(input_size=10, hidden_size=20) + torch.manual_seed(42) + h = nn.LSTM(input_size=10, hidden_size=40) + assert not comparator(g, h) + + # Test LSTM with different num_layers + torch.manual_seed(42) + i = nn.LSTM(input_size=10, hidden_size=20, num_layers=1) + torch.manual_seed(42) + j = nn.LSTM(input_size=10, hidden_size=20, num_layers=2) + assert not comparator(i, j) + + # Test LSTM with different bidirectional + torch.manual_seed(42) + k = nn.LSTM(input_size=10, hidden_size=20, bidirectional=False) + torch.manual_seed(42) + l = nn.LSTM(input_size=10, hidden_size=20, bidirectional=True) + assert not comparator(k, l) + + # Test LSTM with different batch_first + torch.manual_seed(42) + m = nn.LSTM(input_size=10, hidden_size=20, batch_first=False) + torch.manual_seed(42) + n = nn.LSTM(input_size=10, hidden_size=20, batch_first=True) + assert not comparator(m, n) + + +def test_torch_nn_gru(): + """Test comparator for torch.nn.GRU modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical GRU layers + torch.manual_seed(42) + a = nn.GRU(input_size=10, hidden_size=20, num_layers=2) + torch.manual_seed(42) + b = nn.GRU(input_size=10, hidden_size=20, num_layers=2) + assert comparator(a, b) + + # Test GRU with different hidden_size + torch.manual_seed(42) + c = nn.GRU(input_size=10, hidden_size=20) + torch.manual_seed(42) + d = nn.GRU(input_size=10, hidden_size=40) + assert not comparator(c, d) + + # Test GRU vs LSTM (different types) + torch.manual_seed(42) + e = nn.GRU(input_size=10, hidden_size=20) + torch.manual_seed(42) + f = nn.LSTM(input_size=10, hidden_size=20) + assert not comparator(e, f) + + +def test_torch_nn_layernorm(): + """Test comparator for torch.nn.LayerNorm modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical LayerNorm layers + torch.manual_seed(42) + a = nn.LayerNorm(normalized_shape=[10]) + torch.manual_seed(42) + b = nn.LayerNorm(normalized_shape=[10]) + assert comparator(a, b) + + # Test LayerNorm with different normalized_shape + torch.manual_seed(42) + c = nn.LayerNorm(normalized_shape=[10]) + torch.manual_seed(42) + d = nn.LayerNorm(normalized_shape=[20]) + assert not comparator(c, d) + + # Test LayerNorm with different eps + torch.manual_seed(42) + e = nn.LayerNorm(normalized_shape=[10], eps=1e-5) + torch.manual_seed(42) + f = nn.LayerNorm(normalized_shape=[10], eps=1e-3) + assert not comparator(e, f) + + # Test LayerNorm with and without elementwise_affine + torch.manual_seed(42) + g = nn.LayerNorm(normalized_shape=[10], elementwise_affine=True) + torch.manual_seed(42) + h = nn.LayerNorm(normalized_shape=[10], elementwise_affine=False) + assert not comparator(g, h) + + +def test_torch_nn_multihead_attention(): + """Test comparator for torch.nn.MultiheadAttention modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical MultiheadAttention layers + torch.manual_seed(42) + a = nn.MultiheadAttention(embed_dim=64, num_heads=8) + torch.manual_seed(42) + b = nn.MultiheadAttention(embed_dim=64, num_heads=8) + assert comparator(a, b) + + # Test MultiheadAttention with different weights + torch.manual_seed(42) + c = nn.MultiheadAttention(embed_dim=64, num_heads=8) + torch.manual_seed(123) + d = nn.MultiheadAttention(embed_dim=64, num_heads=8) + assert not comparator(c, d) + + # Test MultiheadAttention with different embed_dim + torch.manual_seed(42) + e = nn.MultiheadAttention(embed_dim=64, num_heads=8) + torch.manual_seed(42) + f = nn.MultiheadAttention(embed_dim=128, num_heads=8) + assert not comparator(e, f) + + # Test MultiheadAttention with different num_heads + torch.manual_seed(42) + g = nn.MultiheadAttention(embed_dim=64, num_heads=8) + torch.manual_seed(42) + h = nn.MultiheadAttention(embed_dim=64, num_heads=4) + assert not comparator(g, h) + + # Test MultiheadAttention with different dropout + torch.manual_seed(42) + i = nn.MultiheadAttention(embed_dim=64, num_heads=8, dropout=0.0) + torch.manual_seed(42) + j = nn.MultiheadAttention(embed_dim=64, num_heads=8, dropout=0.1) + assert not comparator(i, j) + + +def test_torch_nn_sequential(): + """Test comparator for torch.nn.Sequential modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical Sequential modules + torch.manual_seed(42) + a = nn.Sequential( + nn.Linear(10, 20), + nn.ReLU(), + nn.Linear(20, 5) + ) + torch.manual_seed(42) + b = nn.Sequential( + nn.Linear(10, 20), + nn.ReLU(), + nn.Linear(20, 5) + ) + assert comparator(a, b) + + # Test Sequential with different weights + torch.manual_seed(42) + c = nn.Sequential( + nn.Linear(10, 20), + nn.ReLU(), + nn.Linear(20, 5) + ) + torch.manual_seed(123) + d = nn.Sequential( + nn.Linear(10, 20), + nn.ReLU(), + nn.Linear(20, 5) + ) + assert not comparator(c, d) + + # Test Sequential with different number of layers + torch.manual_seed(42) + e = nn.Sequential( + nn.Linear(10, 20), + nn.ReLU() + ) + torch.manual_seed(42) + f = nn.Sequential( + nn.Linear(10, 20), + nn.ReLU(), + nn.Linear(20, 5) + ) + assert not comparator(e, f) + + # Test Sequential with different layer types + torch.manual_seed(42) + g = nn.Sequential( + nn.Linear(10, 20), + nn.ReLU() + ) + torch.manual_seed(42) + h = nn.Sequential( + nn.Linear(10, 20), + nn.Sigmoid() + ) + assert not comparator(g, h) + + +def test_torch_nn_modulelist(): + """Test comparator for torch.nn.ModuleList modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical ModuleList + torch.manual_seed(42) + a = nn.ModuleList([nn.Linear(10, 10) for _ in range(3)]) + torch.manual_seed(42) + b = nn.ModuleList([nn.Linear(10, 10) for _ in range(3)]) + assert comparator(a, b) + + # Test ModuleList with different number of modules + torch.manual_seed(42) + c = nn.ModuleList([nn.Linear(10, 10) for _ in range(3)]) + torch.manual_seed(42) + d = nn.ModuleList([nn.Linear(10, 10) for _ in range(4)]) + assert not comparator(c, d) + + +def test_torch_nn_moduledict(): + """Test comparator for torch.nn.ModuleDict modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical ModuleDict + torch.manual_seed(42) + a = nn.ModuleDict({ + "fc1": nn.Linear(10, 20), + "fc2": nn.Linear(20, 5) + }) + torch.manual_seed(42) + b = nn.ModuleDict({ + "fc1": nn.Linear(10, 20), + "fc2": nn.Linear(20, 5) + }) + assert comparator(a, b) + + # Test ModuleDict with different keys + torch.manual_seed(42) + c = nn.ModuleDict({ + "fc1": nn.Linear(10, 20), + "fc2": nn.Linear(20, 5) + }) + torch.manual_seed(42) + d = nn.ModuleDict({ + "layer1": nn.Linear(10, 20), + "layer2": nn.Linear(20, 5) + }) + assert not comparator(c, d) + + +def test_torch_nn_custom_module(): + """Test comparator for custom torch.nn.Module subclasses.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + class SimpleNet(nn.Module): + def __init__(self, hidden_size): + super().__init__() + self.fc1 = nn.Linear(10, hidden_size) + self.relu = nn.ReLU() + self.fc2 = nn.Linear(hidden_size, 5) + + def forward(self, x): + x = self.fc1(x) + x = self.relu(x) + x = self.fc2(x) + return x + + # Test identical custom modules + torch.manual_seed(42) + a = SimpleNet(hidden_size=20) + torch.manual_seed(42) + b = SimpleNet(hidden_size=20) + assert comparator(a, b) + + # Test custom modules with different weights + torch.manual_seed(42) + c = SimpleNet(hidden_size=20) + torch.manual_seed(123) + d = SimpleNet(hidden_size=20) + assert not comparator(c, d) + + # Test custom modules with different architecture + torch.manual_seed(42) + e = SimpleNet(hidden_size=20) + torch.manual_seed(42) + f = SimpleNet(hidden_size=40) + assert not comparator(e, f) + + +def test_torch_nn_nested_modules(): + """Test comparator for nested torch.nn.Module structures.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + class EncoderBlock(nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + self.conv = nn.Conv2d(in_channels, out_channels, 3, padding=1) + self.bn = nn.BatchNorm2d(out_channels) + self.relu = nn.ReLU() + + def forward(self, x): + return self.relu(self.bn(self.conv(x))) + + class Encoder(nn.Module): + def __init__(self): + super().__init__() + self.block1 = EncoderBlock(3, 16) + self.block2 = EncoderBlock(16, 32) + self.pool = nn.MaxPool2d(2) + + def forward(self, x): + x = self.block1(x) + x = self.pool(x) + x = self.block2(x) + x = self.pool(x) + return x + + # Test identical nested modules + torch.manual_seed(42) + a = Encoder() + torch.manual_seed(42) + b = Encoder() + assert comparator(a, b) + + # Test nested modules with different weights + torch.manual_seed(42) + c = Encoder() + torch.manual_seed(123) + d = Encoder() + assert not comparator(c, d) + + +def test_torch_nn_transformer(): + """Test comparator for torch.nn.Transformer modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test identical Transformer + torch.manual_seed(42) + a = nn.Transformer(d_model=64, nhead=4, num_encoder_layers=2, num_decoder_layers=2) + torch.manual_seed(42) + b = nn.Transformer(d_model=64, nhead=4, num_encoder_layers=2, num_decoder_layers=2) + assert comparator(a, b) + + # Test Transformer with different d_model + torch.manual_seed(42) + c = nn.Transformer(d_model=64, nhead=4) + torch.manual_seed(42) + d = nn.Transformer(d_model=128, nhead=4) + assert not comparator(c, d) + + # Test Transformer with different nhead + torch.manual_seed(42) + e = nn.Transformer(d_model=64, nhead=4) + torch.manual_seed(42) + f = nn.Transformer(d_model=64, nhead=8) + assert not comparator(e, f) + + # Test TransformerEncoder + torch.manual_seed(42) + encoder_layer_a = nn.TransformerEncoderLayer(d_model=64, nhead=4) + g = nn.TransformerEncoder(encoder_layer_a, num_layers=2) + torch.manual_seed(42) + encoder_layer_b = nn.TransformerEncoderLayer(d_model=64, nhead=4) + h = nn.TransformerEncoder(encoder_layer_b, num_layers=2) + assert comparator(g, h) + + +def test_torch_nn_parameter_buffer_modification(): + """Test comparator detects parameter and buffer modifications.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test that modifying a parameter is detected + torch.manual_seed(42) + a = nn.Linear(10, 5) + torch.manual_seed(42) + b = nn.Linear(10, 5) + assert comparator(a, b) + + # Modify a parameter + with torch.no_grad(): + b.weight[0, 0] = 999.0 + assert not comparator(a, b) + + # Test that modifying a buffer is detected (BatchNorm running_mean) + torch.manual_seed(42) + c = nn.BatchNorm2d(16) + torch.manual_seed(42) + d = nn.BatchNorm2d(16) + assert comparator(c, d) + + # Modify a buffer + d.running_mean[0] = 999.0 + assert not comparator(c, d) + + +def test_torch_nn_device_placement(): + """Test comparator handles modules on different devices.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Create modules on CPU + torch.manual_seed(42) + cpu_module = nn.Linear(10, 5) + torch.manual_seed(42) + cpu_module2 = nn.Linear(10, 5) + assert comparator(cpu_module, cpu_module2) + + # If CUDA is available, test device mismatch + if torch.cuda.is_available(): + torch.manual_seed(42) + cpu_mod = nn.Linear(10, 5) + torch.manual_seed(42) + cuda_mod = nn.Linear(10, 5).cuda() + assert not comparator(cpu_mod, cuda_mod) + + +def test_torch_nn_conv1d_conv3d(): + """Test comparator for Conv1d and Conv3d modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test Conv1d + torch.manual_seed(42) + a = nn.Conv1d(3, 16, kernel_size=3) + torch.manual_seed(42) + b = nn.Conv1d(3, 16, kernel_size=3) + assert comparator(a, b) + + # Test Conv1d with different out_channels + torch.manual_seed(42) + c = nn.Conv1d(3, 16, kernel_size=3) + torch.manual_seed(42) + d = nn.Conv1d(3, 32, kernel_size=3) + assert not comparator(c, d) + + # Test Conv3d + torch.manual_seed(42) + e = nn.Conv3d(3, 16, kernel_size=3) + torch.manual_seed(42) + f = nn.Conv3d(3, 16, kernel_size=3) + assert comparator(e, f) + + # Test Conv1d vs Conv2d (different types) + torch.manual_seed(42) + g = nn.Conv1d(3, 16, kernel_size=3) + torch.manual_seed(42) + h = nn.Conv2d(3, 16, kernel_size=3) + assert not comparator(g, h) + + +def test_torch_nn_flatten_unflatten(): + """Test comparator for Flatten and Unflatten modules.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test Flatten + a = nn.Flatten() + b = nn.Flatten() + assert comparator(a, b) + + # Test Flatten with different start_dim + c = nn.Flatten(start_dim=1) + d = nn.Flatten(start_dim=0) + assert not comparator(c, d) + + # Test Unflatten + e = nn.Unflatten(dim=1, unflattened_size=(2, 5)) + f = nn.Unflatten(dim=1, unflattened_size=(2, 5)) + assert comparator(e, f) + + +def test_torch_nn_identity(): + """Test comparator for Identity module.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # Test Identity + a = nn.Identity() + b = nn.Identity() + assert comparator(a, b) + + # Test Identity vs Linear (different types) + torch.manual_seed(42) + c = nn.Identity() + d = nn.Linear(10, 10) + assert not comparator(c, d) + + +def test_torch_nn_with_superset(): + """Test comparator superset_obj mode with nn.Module.""" + try: + import torch + from torch import nn + except ImportError: + pytest.skip() + + # For nn.Module, superset_obj should still work + torch.manual_seed(42) + a = nn.Linear(10, 5) + torch.manual_seed(42) + b = nn.Linear(10, 5) + + # superset_obj=True should pass for identical modules + assert comparator(a, b, superset_obj=True) + + # Different modules should still fail + torch.manual_seed(42) + c = nn.Linear(10, 5) + torch.manual_seed(123) + d = nn.Linear(10, 5) + assert not comparator(c, d, superset_obj=True) + + def test_jax(): try: import jax.numpy as jnp @@ -1687,7 +2926,6 @@ def test_torch_runtime_error_wrapping(): class TorchRuntimeError(Exception): """Mock TorchRuntimeError for testing.""" - # Monkey-patch the __module__ to match torch._dynamo.exc TorchRuntimeError.__module__ = "torch._dynamo.exc" diff --git a/tests/test_function_discovery.py b/tests/test_function_discovery.py index 1ec655652..79907fcf5 100644 --- a/tests/test_function_discovery.py +++ b/tests/test_function_discovery.py @@ -950,3 +950,203 @@ def test_filter_functions_non_overlapping_tests_root(): # Strict check: exactly 2 functions remaining assert count == 2, f"Expected exactly 2 functions, got {count}" + + +def test_filter_functions_project_inside_tests_folder(): + """Test that source files are not filtered when project is inside a folder named 'tests'. + + This is a critical regression test for projects located at paths like: + - /home/user/tests/myproject/ + - /Users/dev/tests/n8n/ + + The fix ensures that directory pattern matching (e.g., /tests/) is only checked + on the relative path from project_root, not on the full absolute path. + """ + with tempfile.TemporaryDirectory() as outer_temp_dir_str: + outer_temp_dir = Path(outer_temp_dir_str) + + # Create a "tests" folder to simulate /home/user/tests/ + tests_parent_folder = outer_temp_dir / "tests" + tests_parent_folder.mkdir() + + # Create project inside the "tests" folder - simulates /home/user/tests/myproject/ + project_dir = tests_parent_folder / "myproject" + project_dir.mkdir() + + # Create source file inside the project + src_dir = project_dir / "src" + src_dir.mkdir() + source_file = src_dir / "utils.py" + with source_file.open("w") as f: + f.write(""" +def deep_copy(obj): + \"\"\"Deep copy an object.\"\"\" + import copy + return copy.deepcopy(obj) + +def compare_values(a, b): + \"\"\"Compare two values.\"\"\" + return a == b +""") + + # Create another source file directly in project root + root_source_file = project_dir / "main.py" + with root_source_file.open("w") as f: + f.write(""" +def main(): + \"\"\"Main entry point.\"\"\" + return 0 +""") + + # Create actual test files that should be filtered + project_tests_dir = project_dir / "test" + project_tests_dir.mkdir() + test_file = project_tests_dir / "test_utils.py" + with test_file.open("w") as f: + f.write(""" +def test_deep_copy(): + return True +""") + + # Discover functions + all_functions = {} + for file_path in [source_file, root_source_file, test_file]: + discovered = find_all_functions_in_file(file_path) + all_functions.update(discovered) + + # Test: project at /outer/tests/myproject with tests_root overlapping + # This simulates: /home/user/tests/n8n with tests_root = /home/user/tests/n8n + with unittest.mock.patch( + "codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={} + ): + filtered, count = filter_functions( + all_functions, + tests_root=project_dir, # Same as project_root (overlapping) + ignore_paths=[], + project_root=project_dir, # /outer/tests/myproject + module_root=project_dir, + ) + + # Strict check: source files should NOT be filtered even though + # the full path contains "/tests/" in the parent directory + expected_files = {source_file, root_source_file} + actual_files = set(filtered.keys()) + + assert actual_files == expected_files, ( + f"Source files were incorrectly filtered when project is inside 'tests' folder.\n" + f"Expected files: {expected_files}\n" + f"Got files: {actual_files}\n" + f"Project path: {project_dir}\n" + f"This indicates the /tests/ pattern matched the parent directory path." + ) + + # Verify the correct functions are present + source_functions = sorted([fn.function_name for fn in filtered.get(source_file, [])]) + assert source_functions == ["compare_values", "deep_copy"], ( + f"Expected ['compare_values', 'deep_copy'], got {source_functions}" + ) + + root_functions = [fn.function_name for fn in filtered.get(root_source_file, [])] + assert root_functions == ["main"], ( + f"Expected ['main'], got {root_functions}" + ) + + # Strict check: exactly 3 functions (2 from utils.py + 1 from main.py) + assert count == 3, ( + f"Expected exactly 3 functions, got {count}. " + f"Some source files may have been incorrectly filtered." + ) + + # Verify test file was properly filtered (should not be in results) + assert test_file not in filtered, ( + f"Test file {test_file} should have been filtered but wasn't" + ) + + +def test_filter_functions_typescript_project_in_tests_folder(): + """Test TypeScript-like project structure inside a folder named 'tests'. + + This simulates the n8n project structure: + /home/user/tests/n8n/packages/workflow/src/utils.ts + + Ensures that TypeScript source files are not incorrectly filtered + when the parent directory happens to be named 'tests'. + """ + with tempfile.TemporaryDirectory() as outer_temp_dir_str: + outer_temp_dir = Path(outer_temp_dir_str) + + # Simulate: /home/user/tests/n8n + tests_folder = outer_temp_dir / "tests" + tests_folder.mkdir() + n8n_project = tests_folder / "n8n" + n8n_project.mkdir() + + # Simulate: packages/workflow/src/utils.py (using .py for testing) + packages_dir = n8n_project / "packages" + packages_dir.mkdir() + workflow_dir = packages_dir / "workflow" + workflow_dir.mkdir() + src_dir = workflow_dir / "src" + src_dir.mkdir() + + # Source file deep in the monorepo structure + utils_file = src_dir / "utils.py" + with utils_file.open("w") as f: + f.write(""" +def deep_copy(source): + \"\"\"Create a deep copy of the source object.\"\"\" + if source is None: + return None + return source.copy() if hasattr(source, 'copy') else source + +def is_object_empty(obj): + \"\"\"Check if an object is empty.\"\"\" + return len(obj) == 0 if obj else True +""") + + # Create test directory inside the package (simulating packages/workflow/test/) + test_dir = workflow_dir / "test" + test_dir.mkdir() + test_file = test_dir / "utils.test.py" + with test_file.open("w") as f: + f.write(""" +def test_deep_copy(): + return True + +def test_is_object_empty(): + return True +""") + + # Discover functions + all_functions = {} + for file_path in [utils_file, test_file]: + discovered = find_all_functions_in_file(file_path) + all_functions.update(discovered) + + # Test with module_root = packages (typical TypeScript monorepo setup) + with unittest.mock.patch( + "codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={} + ): + filtered, count = filter_functions( + all_functions, + tests_root=packages_dir, # Overlapping with module_root + ignore_paths=[], + project_root=n8n_project, # /outer/tests/n8n + module_root=packages_dir, # /outer/tests/n8n/packages + ) + + # Strict check: only the source file should remain + assert set(filtered.keys()) == {utils_file}, ( + f"Expected only {utils_file} but got {set(filtered.keys())}.\n" + f"Source files in /outer/tests/n8n/packages/workflow/src/ were incorrectly filtered.\n" + f"The /tests/ pattern in the parent path should not affect filtering." + ) + + # Verify the correct functions are present + filtered_functions = sorted([fn.function_name for fn in filtered.get(utils_file, [])]) + assert filtered_functions == ["deep_copy", "is_object_empty"], ( + f"Expected ['deep_copy', 'is_object_empty'], got {filtered_functions}" + ) + + # Strict check: exactly 2 functions + assert count == 2, f"Expected exactly 2 functions, got {count}" \ No newline at end of file diff --git a/tests/test_languages/test_import_resolver.py b/tests/test_languages/test_import_resolver.py index 0f50a8833..5b27179c5 100644 --- a/tests/test_languages/test_import_resolver.py +++ b/tests/test_languages/test_import_resolver.py @@ -8,7 +8,7 @@ import pytest from codeflash.languages.javascript.import_resolver import HelperSearchContext, ImportResolver, MultiFileHelperFinder -from codeflash.languages.treesitter_utils import ImportInfo +from codeflash.languages.javascript.treesitter import ImportInfo class TestImportResolver: @@ -286,7 +286,7 @@ class TestExportInfo: @pytest.fixture def js_analyzer(self): """Create a JavaScript analyzer.""" - from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage return TreeSitterAnalyzer(TreeSitterLanguage.JAVASCRIPT) @@ -388,7 +388,7 @@ class TestCommonJSRequire: @pytest.fixture def js_analyzer(self): """Create a JavaScript analyzer.""" - from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage return TreeSitterAnalyzer(TreeSitterLanguage.JAVASCRIPT) @@ -470,14 +470,14 @@ class TestCommonJSExports: @pytest.fixture def js_analyzer(self): """Create a JavaScript analyzer.""" - from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage return TreeSitterAnalyzer(TreeSitterLanguage.JAVASCRIPT) @pytest.fixture def ts_analyzer(self): """Create a TypeScript analyzer.""" - from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage return TreeSitterAnalyzer(TreeSitterLanguage.TYPESCRIPT) diff --git a/tests/test_languages/test_javascript_integration.py b/tests/test_languages/test_javascript_integration.py new file mode 100644 index 000000000..dfcce91fe --- /dev/null +++ b/tests/test_languages/test_javascript_integration.py @@ -0,0 +1,312 @@ +"""E2E tests for JavaScript/TypeScript optimization flow with backend. + +These tests call the actual backend /testgen API endpoint and verify: +1. Language parameter is correctly passed to backend +2. Backend validates generated code with correct parser (JS vs TS) +3. CLI receives and processes tests correctly + +Similar to test_validate_python_code.py but for JavaScript/TypeScript. +""" + +from pathlib import Path +from unittest.mock import patch + +import pytest + +from codeflash.api.aiservice import AiServiceClient +from codeflash.discovery.functions_to_optimize import FunctionToOptimize +from codeflash.languages.base import Language +from codeflash.models.models import CodeString, OptimizedCandidateSource + + +def skip_if_js_not_supported(): + """Skip test if JavaScript/TypeScript languages are not supported.""" + try: + from codeflash.languages import get_language_support + get_language_support(Language.JAVASCRIPT) + except Exception as e: + pytest.skip(f"JavaScript/TypeScript language support not available: {e}") + + +class TestJavaScriptCodeStringValidation: + """Tests for JavaScript CodeString validation - mirrors test_validate_python_code.py.""" + + def test_javascript_string(self): + """Test valid JavaScript code string.""" + skip_if_js_not_supported() + code = CodeString(code="console.log('Hello, World!');", language="javascript") + assert code.code == "console.log('Hello, World!');" + + def test_valid_javascript_code(self): + """Test that valid JavaScript code passes validation.""" + skip_if_js_not_supported() + valid_code = "const x = 1;\nconst y = x + 2;\nconsole.log(y);" + cs = CodeString(code=valid_code, language="javascript") + assert cs.code == valid_code + + def test_invalid_javascript_code_syntax(self): + """Test that invalid JavaScript code fails validation.""" + skip_if_js_not_supported() + from pydantic import ValidationError + + invalid_code = "const x = 1;\nconsole.log(x" # Missing parenthesis + with pytest.raises(ValidationError) as exc_info: + CodeString(code=invalid_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + def test_empty_javascript_code(self): + """Test that empty code passes validation.""" + skip_if_js_not_supported() + empty_code = "" + cs = CodeString(code=empty_code, language="javascript") + assert cs.code == empty_code + + +class TestTypeScriptCodeStringValidation: + """Tests for TypeScript CodeString validation.""" + + def test_typescript_string(self): + """Test valid TypeScript code string.""" + skip_if_js_not_supported() + code = CodeString(code="const x: number = 1;", language="typescript") + assert code.code == "const x: number = 1;" + + def test_valid_typescript_code(self): + """Test that valid TypeScript code passes validation.""" + skip_if_js_not_supported() + valid_code = "function add(a: number, b: number): number { return a + b; }" + cs = CodeString(code=valid_code, language="typescript") + assert cs.code == valid_code + + def test_typescript_type_assertion_valid(self): + """TypeScript type assertions should pass TypeScript validation.""" + skip_if_js_not_supported() + ts_code = "const value = 4.9 as unknown as number;" + cs = CodeString(code=ts_code, language="typescript") + assert cs.code == ts_code + + def test_typescript_type_assertion_invalid_in_javascript(self): + """TypeScript type assertions should FAIL JavaScript validation. + + This is the critical test - TypeScript syntax like 'as unknown as number' + should fail when validated as JavaScript. + """ + skip_if_js_not_supported() + from pydantic import ValidationError + + ts_code = "const value = 4.9 as unknown as number;" + with pytest.raises(ValidationError) as exc_info: + CodeString(code=ts_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + def test_typescript_interface_valid(self): + """TypeScript interfaces should pass TypeScript validation.""" + skip_if_js_not_supported() + ts_code = "interface User { name: string; age: number; }" + cs = CodeString(code=ts_code, language="typescript") + assert cs.code == ts_code + + def test_typescript_interface_invalid_in_javascript(self): + """TypeScript interfaces should FAIL JavaScript validation.""" + skip_if_js_not_supported() + from pydantic import ValidationError + + ts_code = "interface User { name: string; age: number; }" + with pytest.raises(ValidationError) as exc_info: + CodeString(code=ts_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + def test_typescript_generics_valid(self): + """TypeScript generics should pass TypeScript validation.""" + skip_if_js_not_supported() + ts_code = "function identity(arg: T): T { return arg; }" + cs = CodeString(code=ts_code, language="typescript") + assert cs.code == ts_code + + def test_typescript_generics_invalid_in_javascript(self): + """TypeScript generics should FAIL JavaScript validation.""" + skip_if_js_not_supported() + from pydantic import ValidationError + + ts_code = "function identity(arg: T): T { return arg; }" + with pytest.raises(ValidationError) as exc_info: + CodeString(code=ts_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + +class TestAiServiceClientJavaScript: + """Tests for AiServiceClient with JavaScript/TypeScript - mirrors test_validate_python_code.py.""" + + def test_javascript_generated_candidates_validation(self): + """Test that JavaScript candidates are validated correctly.""" + skip_if_js_not_supported() + ai_service = AiServiceClient() + + # Invalid JavaScript (missing closing parenthesis) + code = """```javascript:file.js +console.log(name +```""" + mock_candidates = [{"source_code": code, "explanation": "", "optimization_id": ""}] + candidates = ai_service._get_valid_candidates(mock_candidates, OptimizedCandidateSource.OPTIMIZE) + assert len(candidates) == 0 + + # Valid JavaScript + code = """```javascript:file.js +console.log('Hello, World!'); +```""" + mock_candidates = [{"source_code": code, "explanation": "", "optimization_id": ""}] + candidates = ai_service._get_valid_candidates(mock_candidates, OptimizedCandidateSource.OPTIMIZE) + assert len(candidates) == 1 + assert candidates[0].source_code.code_strings[0].code == "console.log('Hello, World!');" + + def test_typescript_generated_candidates_validation(self): + """Test that TypeScript candidates are validated correctly.""" + skip_if_js_not_supported() + ai_service = AiServiceClient() + + # Valid TypeScript with type annotations + code = """```typescript:file.ts +function add(a: number, b: number): number { + return a + b; +} +```""" + mock_candidates = [{"source_code": code, "explanation": "", "optimization_id": ""}] + candidates = ai_service._get_valid_candidates(mock_candidates, OptimizedCandidateSource.OPTIMIZE) + assert len(candidates) == 1 + + def test_typescript_type_assertion_in_candidate(self): + """Test that TypeScript type assertions are valid in TS candidates.""" + skip_if_js_not_supported() + ai_service = AiServiceClient() + + # TypeScript-specific syntax should be valid + code = """```typescript:file.ts +const value = 4.9 as unknown as number; +```""" + mock_candidates = [{"source_code": code, "explanation": "", "optimization_id": ""}] + candidates = ai_service._get_valid_candidates(mock_candidates, OptimizedCandidateSource.OPTIMIZE) + assert len(candidates) == 1 + + +class TestBackendLanguageParameter: + """Tests verifying language parameter flows correctly to backend.""" + + def test_testgen_request_includes_typescript_language(self, tmp_path): + """Verify the language parameter is sent as 'typescript' for .ts files.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + + # Set current language to TypeScript + lang_current._current_language = Language.TYPESCRIPT + + ts_file = tmp_path / "utils.ts" + ts_file.write_text(""" +export function add(a: number, b: number): number { + return a + b; +} +""") + + functions = find_all_functions_in_file(ts_file) + func = functions[ts_file][0] + + # Verify function has correct language + assert func.language == "typescript" + + ai_client = AiServiceClient() + captured_payload = None + + def capture_request(*args, **kwargs): + nonlocal captured_payload + if 'payload' in kwargs: + captured_payload = kwargs['payload'] + elif len(args) > 1: + captured_payload = args[1] + # Return a mock response to avoid actual API call + from unittest.mock import MagicMock + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "generated_tests": "// test", + "instrumented_behavior_tests": "// test", + "instrumented_perf_tests": "// test", + } + return mock_response + + with patch.object(ai_client, 'make_ai_service_request', side_effect=capture_request): + ai_client.generate_regression_tests( + source_code_being_tested=ts_file.read_text(), + function_to_optimize=func, + helper_function_names=[], + module_path=ts_file, + test_module_path=tmp_path / "tests" / "utils.test.ts", + test_framework="vitest", + test_timeout=30, + trace_id="test-language-param-ts", + test_index=0, + language="typescript", + ) + + assert captured_payload is not None + assert captured_payload.get('language') == 'typescript', \ + f"Expected language='typescript', got: {captured_payload.get('language')}" + + def test_testgen_request_includes_javascript_language(self, tmp_path): + """Verify the language parameter is sent as 'javascript' for .js files.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + + # Set current language to JavaScript + lang_current._current_language = Language.JAVASCRIPT + + js_file = tmp_path / "utils.js" + js_file.write_text(""" +function add(a, b) { + return a + b; +} +module.exports = { add }; +""") + + functions = find_all_functions_in_file(js_file) + func = functions[js_file][0] + + # Verify function has correct language + assert func.language == "javascript" + + ai_client = AiServiceClient() + captured_payload = None + + def capture_request(*args, **kwargs): + nonlocal captured_payload + if 'payload' in kwargs: + captured_payload = kwargs['payload'] + elif len(args) > 1: + captured_payload = args[1] + from unittest.mock import MagicMock + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "generated_tests": "// test", + "instrumented_behavior_tests": "// test", + "instrumented_perf_tests": "// test", + } + return mock_response + + with patch.object(ai_client, 'make_ai_service_request', side_effect=capture_request): + ai_client.generate_regression_tests( + source_code_being_tested=js_file.read_text(), + function_to_optimize=func, + helper_function_names=[], + module_path=js_file, + test_module_path=tmp_path / "tests" / "utils.test.js", + test_framework="jest", + test_timeout=30, + trace_id="test-language-param-js", + test_index=0, + language="javascript", + ) + + assert captured_payload is not None + assert captured_payload.get('language') == 'javascript', \ + f"Expected language='javascript', got: {captured_payload.get('language')}" diff --git a/tests/test_languages/test_javascript_optimization_flow.py b/tests/test_languages/test_javascript_optimization_flow.py new file mode 100644 index 000000000..7c7ba5aa6 --- /dev/null +++ b/tests/test_languages/test_javascript_optimization_flow.py @@ -0,0 +1,564 @@ +"""End-to-end tests for JavaScript/TypeScript optimization flow. + +These tests verify the full optimization pipeline including: +- Test generation (with mocked backend) +- Language parameter propagation +- Syntax validation with correct parser +- Running and parsing tests + +This is the JavaScript equivalent of test_instrument_tests.py for Python. +""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from codeflash.discovery.functions_to_optimize import FunctionToOptimize +from codeflash.languages.base import Language +from codeflash.models.models import CodeString, FunctionParent +from codeflash.verification.verification_utils import TestConfig + + +def skip_if_js_not_supported(): + """Skip test if JavaScript/TypeScript languages are not supported.""" + try: + from codeflash.languages import get_language_support + + get_language_support(Language.JAVASCRIPT) + except Exception as e: + pytest.skip(f"JavaScript/TypeScript language support not available: {e}") + + +class TestLanguageParameterPropagation: + """Tests verifying language parameter is correctly passed through all layers.""" + + def test_function_to_optimize_has_correct_language_for_typescript(self, tmp_path): + """Verify FunctionToOptimize has language='typescript' for .ts files.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + + ts_file = tmp_path / "utils.ts" + ts_file.write_text(""" +export function add(a: number, b: number): number { + return a + b; +} +""") + + functions = find_all_functions_in_file(ts_file) + assert ts_file in functions + assert len(functions[ts_file]) == 1 + assert functions[ts_file][0].language == "typescript" + + def test_function_to_optimize_has_correct_language_for_javascript(self, tmp_path): + """Verify FunctionToOptimize has language='javascript' for .js files.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + + js_file = tmp_path / "utils.js" + js_file.write_text(""" +function add(a, b) { + return a + b; +} +""") + + functions = find_all_functions_in_file(js_file) + assert js_file in functions + assert len(functions[js_file]) == 1 + assert functions[js_file][0].language == "javascript" + + def test_code_context_preserves_language(self, tmp_path): + """Verify language is preserved in code context extraction.""" + skip_if_js_not_supported() + from codeflash.context.code_context_extractor import get_code_optimization_context + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + + lang_current._current_language = Language.TYPESCRIPT + + ts_file = tmp_path / "utils.ts" + ts_file.write_text(""" +export function add(a: number, b: number): number { + return a + b; +} +""") + + functions = find_all_functions_in_file(ts_file) + func = functions[ts_file][0] + + context = get_code_optimization_context(func, tmp_path) + + assert context.read_writable_code is not None + assert context.read_writable_code.language == "typescript" + + +class TestCodeStringSyntaxValidation: + """Tests verifying CodeString validates with correct parser based on language.""" + + def test_typescript_code_valid_with_typescript_language(self): + """TypeScript code should pass validation when language='typescript'.""" + skip_if_js_not_supported() + + ts_code = "const value = 4.9 as unknown as number;" + code_string = CodeString(code=ts_code, language="typescript") + assert code_string.code == ts_code + + def test_typescript_code_invalid_with_javascript_language(self): + """TypeScript code should FAIL validation when language='javascript'. + + This is the exact bug that was in production - TypeScript code being + validated with JavaScript parser. + """ + skip_if_js_not_supported() + from pydantic import ValidationError + + ts_code = "const value = 4.9 as unknown as number;" + with pytest.raises(ValidationError) as exc_info: + CodeString(code=ts_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + def test_typescript_interface_valid_with_typescript_language(self): + """TypeScript interface should pass validation when language='typescript'.""" + skip_if_js_not_supported() + + ts_code = "interface User { name: string; age: number; }" + code_string = CodeString(code=ts_code, language="typescript") + assert code_string.code == ts_code + + def test_typescript_interface_invalid_with_javascript_language(self): + """TypeScript interface should FAIL validation when language='javascript'.""" + skip_if_js_not_supported() + from pydantic import ValidationError + + ts_code = "interface User { name: string; age: number; }" + with pytest.raises(ValidationError) as exc_info: + CodeString(code=ts_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + +class TestBackendAPIResponseValidation: + """Tests verifying backend API responses are validated with correct parser.""" + + def test_testgen_request_includes_correct_language(self, tmp_path): + """Verify test generation request includes the correct language parameter.""" + skip_if_js_not_supported() + from codeflash.api.aiservice import AiServiceClient + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + + lang_current._current_language = Language.TYPESCRIPT + + ts_file = tmp_path / "utils.ts" + ts_file.write_text(""" +export function add(a: number, b: number): number { + return a + b; +} +""") + + functions = find_all_functions_in_file(ts_file) + func = functions[ts_file][0] + + # Verify function has correct language + assert func.language == "typescript" + + # Mock the AI service request + ai_client = AiServiceClient() + with patch.object(ai_client, 'make_ai_service_request') as mock_request: + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "generated_tests": "// test code", + "instrumented_behavior_tests": "// behavior code", + "instrumented_perf_tests": "// perf code", + } + mock_request.return_value = mock_response + + # Call generate_regression_tests with correct parameters + ai_client.generate_regression_tests( + source_code_being_tested="export function add(a: number, b: number): number { return a + b; }", + function_to_optimize=func, + helper_function_names=[], + module_path=ts_file, + test_module_path=tmp_path / "tests" / "utils.test.ts", + test_framework="vitest", + test_timeout=30, + trace_id="test-trace-id", + test_index=0, + language=func.language, # This is the key - language should be "typescript" + ) + + # Verify the request was made with correct language + assert mock_request.called, "API request should have been made" + call_args = mock_request.call_args + payload = call_args[1].get('payload', call_args[0][1] if len(call_args[0]) > 1 else {}) + assert payload.get('language') == 'typescript', \ + f"Expected language='typescript', got language='{payload.get('language')}'" + + +class TestFunctionOptimizerForJavaScript: + """Tests for FunctionOptimizer with JavaScript/TypeScript functions. + + This is the JavaScript equivalent of test_instrument_tests.py tests. + """ + + @pytest.fixture + def js_project(self, tmp_path): + """Create a minimal JavaScript project for testing.""" + project = tmp_path / "js_project" + project.mkdir() + + # Create source file + src_file = project / "utils.js" + src_file.write_text(""" +function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +module.exports = { fibonacci }; +""") + + # Create test file + tests_dir = project / "tests" + tests_dir.mkdir() + test_file = tests_dir / "utils.test.js" + test_file.write_text(""" +const { fibonacci } = require('../utils'); + +describe('fibonacci', () => { + test('returns 0 for n=0', () => { + expect(fibonacci(0)).toBe(0); + }); + + test('returns 1 for n=1', () => { + expect(fibonacci(1)).toBe(1); + }); + + test('returns 5 for n=5', () => { + expect(fibonacci(5)).toBe(5); + }); +}); +""") + + # Create package.json + package_json = project / "package.json" + package_json.write_text(""" +{ + "name": "test-project", + "devDependencies": { + "jest": "^29.0.0" + } +} +""") + + return project + + @pytest.fixture + def ts_project(self, tmp_path): + """Create a minimal TypeScript project for testing.""" + project = tmp_path / "ts_project" + project.mkdir() + + # Create source file + src_file = project / "utils.ts" + src_file.write_text(""" +export function fibonacci(n: number): number { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} +""") + + # Create test file + tests_dir = project / "tests" + tests_dir.mkdir() + test_file = tests_dir / "utils.test.ts" + test_file.write_text(""" +import { fibonacci } from '../utils'; + +describe('fibonacci', () => { + test('returns 0 for n=0', () => { + expect(fibonacci(0)).toBe(0); + }); + + test('returns 1 for n=1', () => { + expect(fibonacci(1)).toBe(1); + }); +}); +""") + + # Create package.json + package_json = project / "package.json" + package_json.write_text(""" +{ + "name": "test-project", + "devDependencies": { + "vitest": "^1.0.0" + } +} +""") + + return project + + def test_function_optimizer_instantiation_javascript(self, js_project): + """Test FunctionOptimizer can be instantiated for JavaScript.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.optimization.function_optimizer import FunctionOptimizer + + src_file = js_project / "utils.js" + functions = find_all_functions_in_file(src_file) + func = functions[src_file][0] + + func_to_optimize = FunctionToOptimize( + function_name=func.function_name, + file_path=func.file_path, + parents=[FunctionParent(name=p.name, type=p.type) for p in func.parents], + starting_line=func.starting_line, + ending_line=func.ending_line, + language=func.language, + ) + + test_config = TestConfig( + tests_root=js_project / "tests", + tests_project_rootdir=js_project, + project_root_path=js_project, + pytest_cmd="jest", + ) + + optimizer = FunctionOptimizer( + function_to_optimize=func_to_optimize, + test_cfg=test_config, + aiservice_client=MagicMock(), + ) + + assert optimizer is not None + assert optimizer.function_to_optimize.language == "javascript" + + def test_function_optimizer_instantiation_typescript(self, ts_project): + """Test FunctionOptimizer can be instantiated for TypeScript.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.optimization.function_optimizer import FunctionOptimizer + + src_file = ts_project / "utils.ts" + functions = find_all_functions_in_file(src_file) + func = functions[src_file][0] + + func_to_optimize = FunctionToOptimize( + function_name=func.function_name, + file_path=func.file_path, + parents=[FunctionParent(name=p.name, type=p.type) for p in func.parents], + starting_line=func.starting_line, + ending_line=func.ending_line, + language=func.language, + ) + + test_config = TestConfig( + tests_root=ts_project / "tests", + tests_project_rootdir=ts_project, + project_root_path=ts_project, + pytest_cmd="vitest", + ) + + optimizer = FunctionOptimizer( + function_to_optimize=func_to_optimize, + test_cfg=test_config, + aiservice_client=MagicMock(), + ) + + assert optimizer is not None + assert optimizer.function_to_optimize.language == "typescript" + + def test_get_code_optimization_context_javascript(self, js_project): + """Test get_code_optimization_context for JavaScript.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + from codeflash.optimization.function_optimizer import FunctionOptimizer + + lang_current._current_language = Language.JAVASCRIPT + + src_file = js_project / "utils.js" + functions = find_all_functions_in_file(src_file) + func = functions[src_file][0] + + func_to_optimize = FunctionToOptimize( + function_name=func.function_name, + file_path=func.file_path, + parents=[FunctionParent(name=p.name, type=p.type) for p in func.parents], + starting_line=func.starting_line, + ending_line=func.ending_line, + language=func.language, + ) + + test_config = TestConfig( + tests_root=js_project / "tests", + tests_project_rootdir=js_project, + project_root_path=js_project, + pytest_cmd="jest", + ) + + optimizer = FunctionOptimizer( + function_to_optimize=func_to_optimize, + test_cfg=test_config, + aiservice_client=MagicMock(), + ) + + result = optimizer.get_code_optimization_context() + context = result.unwrap() + + assert context is not None + assert context.read_writable_code is not None + assert context.read_writable_code.language == "javascript" + + def test_get_code_optimization_context_typescript(self, ts_project): + """Test get_code_optimization_context for TypeScript.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + from codeflash.optimization.function_optimizer import FunctionOptimizer + + lang_current._current_language = Language.TYPESCRIPT + + src_file = ts_project / "utils.ts" + functions = find_all_functions_in_file(src_file) + func = functions[src_file][0] + + func_to_optimize = FunctionToOptimize( + function_name=func.function_name, + file_path=func.file_path, + parents=[FunctionParent(name=p.name, type=p.type) for p in func.parents], + starting_line=func.starting_line, + ending_line=func.ending_line, + language=func.language, + ) + + test_config = TestConfig( + tests_root=ts_project / "tests", + tests_project_rootdir=ts_project, + project_root_path=ts_project, + pytest_cmd="vitest", + ) + + optimizer = FunctionOptimizer( + function_to_optimize=func_to_optimize, + test_cfg=test_config, + aiservice_client=MagicMock(), + ) + + result = optimizer.get_code_optimization_context() + context = result.unwrap() + + assert context is not None + assert context.read_writable_code is not None + assert context.read_writable_code.language == "typescript" + + +class TestHelperFunctionLanguageAttribute: + """Tests for helper function language attribute (import_resolver.py fix).""" + + def test_helper_functions_have_correct_language_javascript(self, tmp_path): + """Verify helper functions have language='javascript' for .js files.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current, get_language_support + from codeflash.optimization.function_optimizer import FunctionOptimizer + + lang_current._current_language = Language.JAVASCRIPT + + # Create a file with helper functions + src_file = tmp_path / "main.js" + src_file.write_text(""" +function helper() { + return 42; +} + +function main() { + return helper() * 2; +} + +module.exports = { main }; +""") + + functions = find_all_functions_in_file(src_file) + main_func = next(f for f in functions[src_file] if f.function_name == "main") + + func_to_optimize = FunctionToOptimize( + function_name=main_func.function_name, + file_path=main_func.file_path, + parents=[], + starting_line=main_func.starting_line, + ending_line=main_func.ending_line, + language=main_func.language, + ) + + test_config = TestConfig( + tests_root=tmp_path, + tests_project_rootdir=tmp_path, + project_root_path=tmp_path, + pytest_cmd="jest", + ) + + optimizer = FunctionOptimizer( + function_to_optimize=func_to_optimize, + test_cfg=test_config, + aiservice_client=MagicMock(), + ) + + result = optimizer.get_code_optimization_context() + context = result.unwrap() + + # Verify main function has correct language + assert context.read_writable_code.language == "javascript" + + def test_helper_functions_have_correct_language_typescript(self, tmp_path): + """Verify helper functions have language='typescript' for .ts files.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + from codeflash.optimization.function_optimizer import FunctionOptimizer + + lang_current._current_language = Language.TYPESCRIPT + + # Create a file with helper functions + src_file = tmp_path / "main.ts" + src_file.write_text(""" +function helper(): number { + return 42; +} + +export function main(): number { + return helper() * 2; +} +""") + + functions = find_all_functions_in_file(src_file) + main_func = next(f for f in functions[src_file] if f.function_name == "main") + + func_to_optimize = FunctionToOptimize( + function_name=main_func.function_name, + file_path=main_func.file_path, + parents=[], + starting_line=main_func.starting_line, + ending_line=main_func.ending_line, + language=main_func.language, + ) + + test_config = TestConfig( + tests_root=tmp_path, + tests_project_rootdir=tmp_path, + project_root_path=tmp_path, + pytest_cmd="vitest", + ) + + optimizer = FunctionOptimizer( + function_to_optimize=func_to_optimize, + test_cfg=test_config, + aiservice_client=MagicMock(), + ) + + result = optimizer.get_code_optimization_context() + context = result.unwrap() + + # Verify main function has correct language + assert context.read_writable_code.language == "typescript" diff --git a/tests/test_languages/test_javascript_run_and_parse.py b/tests/test_languages/test_javascript_run_and_parse.py new file mode 100644 index 000000000..4222b001c --- /dev/null +++ b/tests/test_languages/test_javascript_run_and_parse.py @@ -0,0 +1,516 @@ +"""End-to-end tests for JavaScript/TypeScript test execution and result parsing. + +These tests verify the FULL optimization pipeline including: +- Test instrumentation +- Running instrumented tests with Vitest/Jest +- Parsing test results (stdout, timing, return values) +- Benchmarking with multiple loops + +This is the JavaScript equivalent of test_instrument_tests.py for Python. + +NOTE: These tests require: +- Node.js installed +- npm packages installed in the test fixture directories +- The codeflash npm package + +Tests will be skipped if dependencies are not available. +""" + +import os +import shutil +import subprocess +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from codeflash.discovery.functions_to_optimize import FunctionToOptimize +from codeflash.languages.base import Language +from codeflash.models.models import FunctionParent, TestFile, TestFiles, TestType, TestingMode +from codeflash.verification.verification_utils import TestConfig + + +def is_node_available(): + """Check if Node.js is available.""" + try: + result = subprocess.run(["node", "--version"], capture_output=True, text=True) + return result.returncode == 0 + except FileNotFoundError: + return False + + +def is_npm_available(): + """Check if npm is available.""" + try: + result = subprocess.run(["npm", "--version"], capture_output=True, text=True) + return result.returncode == 0 + except FileNotFoundError: + return False + + +def has_node_modules(project_dir: Path) -> bool: + """Check if node_modules exists in project directory.""" + return (project_dir / "node_modules").exists() + + +def install_dependencies(project_dir: Path) -> bool: + """Install npm dependencies in project directory.""" + if has_node_modules(project_dir): + return True + try: + result = subprocess.run( + ["npm", "install"], + cwd=project_dir, + capture_output=True, + text=True, + timeout=120 + ) + return result.returncode == 0 + except Exception: + return False + + +def skip_if_js_runtime_not_available(): + """Skip test if JavaScript runtime is not available.""" + if not is_node_available(): + pytest.skip("Node.js not available") + if not is_npm_available(): + pytest.skip("npm not available") + + +def skip_if_js_not_supported(): + """Skip test if JavaScript/TypeScript languages are not supported.""" + try: + from codeflash.languages import get_language_support + get_language_support(Language.JAVASCRIPT) + except Exception as e: + pytest.skip(f"JavaScript/TypeScript language support not available: {e}") + + +class TestJavaScriptInstrumentation: + """Tests for JavaScript test instrumentation.""" + + @pytest.fixture + def js_project_dir(self, tmp_path): + """Create a temporary JavaScript project with Jest.""" + project_dir = tmp_path / "js_project" + project_dir.mkdir() + + # Create source file + src_file = project_dir / "math.js" + src_file.write_text(""" +function add(a, b) { + return a + b; +} + +function multiply(a, b) { + return a * b; +} + +module.exports = { add, multiply }; +""") + + # Create test file + tests_dir = project_dir / "__tests__" + tests_dir.mkdir() + test_file = tests_dir / "math.test.js" + test_file.write_text(""" +const { add, multiply } = require('../math'); + +describe('math functions', () => { + test('add returns sum', () => { + expect(add(2, 3)).toBe(5); + }); + + test('multiply returns product', () => { + expect(multiply(2, 3)).toBe(6); + }); +}); +""") + + # Create package.json + package_json = project_dir / "package.json" + package_json.write_text("""{ + "name": "test-project", + "version": "1.0.0", + "scripts": { + "test": "jest" + }, + "devDependencies": { + "jest": "^29.0.0", + "jest-junit": "^16.0.0" + } +}""") + + # Create jest.config.js + jest_config = project_dir / "jest.config.js" + jest_config.write_text(""" +module.exports = { + testEnvironment: 'node', + reporters: ['default', 'jest-junit'], +}; +""") + + return project_dir + + def test_instrument_javascript_test_file(self, js_project_dir): + """Test that JavaScript test instrumentation module can be imported.""" + skip_if_js_not_supported() + from codeflash.languages import get_language_support + # Verify the instrumentation module can be imported + from codeflash.languages.javascript.instrument import inject_profiling_into_existing_js_test + + # Get JavaScript support + js_support = get_language_support(Language.JAVASCRIPT) + + # Create function info + func_info = FunctionToOptimize( + function_name="add", + file_path=js_project_dir / "math.js", + parents=[], + starting_line=2, + ending_line=4, + language="javascript", + ) + + # Verify function has correct language + assert func_info.language == "javascript" + + # Verify test file exists + test_file = js_project_dir / "__tests__" / "math.test.js" + assert test_file.exists() + + # Note: Full instrumentation test requires call_positions discovery + # which is done by the FunctionOptimizer. Here we just verify the + # infrastructure is in place. + + +class TestTypeScriptInstrumentation: + """Tests for TypeScript test instrumentation.""" + + @pytest.fixture + def ts_project_dir(self, tmp_path): + """Create a temporary TypeScript project with Vitest.""" + project_dir = tmp_path / "ts_project" + project_dir.mkdir() + + # Create source file + src_file = project_dir / "math.ts" + src_file.write_text(""" +export function add(a: number, b: number): number { + return a + b; +} + +export function multiply(a: number, b: number): number { + return a * b; +} +""") + + # Create test file + tests_dir = project_dir / "tests" + tests_dir.mkdir() + test_file = tests_dir / "math.test.ts" + test_file.write_text(""" +import { describe, test, expect } from 'vitest'; +import { add, multiply } from '../math'; + +describe('math functions', () => { + test('add returns sum', () => { + expect(add(2, 3)).toBe(5); + }); + + test('multiply returns product', () => { + expect(multiply(2, 3)).toBe(6); + }); +}); +""") + + # Create package.json + package_json = project_dir / "package.json" + package_json.write_text("""{ + "name": "test-project", + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "vitest run" + }, + "devDependencies": { + "vitest": "^1.0.0", + "typescript": "^5.0.0" + } +}""") + + # Create vitest.config.ts + vitest_config = project_dir / "vitest.config.ts" + vitest_config.write_text(""" +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + reporters: ['verbose', 'junit'], + outputFile: './junit.xml', + }, +}); +""") + + # Create tsconfig.json + tsconfig = project_dir / "tsconfig.json" + tsconfig.write_text("""{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true + } +}""") + + return project_dir + + def test_instrument_typescript_test_file(self, ts_project_dir): + """Test that TypeScript test instrumentation module can be imported.""" + skip_if_js_not_supported() + from codeflash.languages import get_language_support + # Verify the instrumentation module can be imported + from codeflash.languages.javascript.instrument import inject_profiling_into_existing_js_test + + test_file = ts_project_dir / "tests" / "math.test.ts" + + # Get TypeScript support + ts_support = get_language_support(Language.TYPESCRIPT) + + # Create function info + func_info = FunctionToOptimize( + function_name="add", + file_path=ts_project_dir / "math.ts", + parents=[], + starting_line=2, + ending_line=4, + language="typescript", + ) + + # Verify function has correct language + assert func_info.language == "typescript" + + # Verify test file exists + assert test_file.exists() + + # Note: Full instrumentation test requires call_positions discovery + # which is done by the FunctionOptimizer. Here we just verify the + # infrastructure is in place. + + +class TestRunAndParseJavaScriptTests: + """Tests for running and parsing JavaScript test results. + + These tests require actual npm dependencies to be installed. + They will be skipped if dependencies are not available. + """ + + @pytest.fixture + def vitest_project(self): + """Get the Vitest sample project with dependencies installed.""" + project_root = Path(__file__).parent.parent.parent + vitest_dir = project_root / "code_to_optimize" / "js" / "code_to_optimize_vitest" + + if not vitest_dir.exists(): + pytest.skip("code_to_optimize_vitest directory not found") + + skip_if_js_runtime_not_available() + + # Try to install dependencies if not present + if not has_node_modules(vitest_dir): + if not install_dependencies(vitest_dir): + pytest.skip("Could not install npm dependencies") + + return vitest_dir + + def test_run_behavioral_tests_vitest(self, vitest_project): + """Test running behavioral tests with Vitest.""" + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import get_language_support + + ts_support = get_language_support(Language.TYPESCRIPT) + + # Find the fibonacci function + fib_file = vitest_project / "fibonacci.ts" + functions = find_all_functions_in_file(fib_file) + fib_func = next(f for f in functions[fib_file] if f.function_name == "fibonacci") + + # Verify language is correct + assert fib_func.language == "typescript" + + # Discover tests + test_root = vitest_project / "tests" + tests = ts_support.discover_tests(test_root, [fib_func]) + + # There should be tests for fibonacci + assert len(tests) > 0 or fib_func.qualified_name in tests + + def test_function_optimizer_run_and_parse_typescript(self, vitest_project): + """Test FunctionOptimizer.run_and_parse_tests for TypeScript. + + This is the JavaScript equivalent of the Python test in test_instrument_tests.py. + """ + skip_if_js_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + from codeflash.optimization.function_optimizer import FunctionOptimizer + + lang_current._current_language = Language.TYPESCRIPT + + # Find the fibonacci function + fib_file = vitest_project / "fibonacci.ts" + functions = find_all_functions_in_file(fib_file) + fib_func_info = next(f for f in functions[fib_file] if f.function_name == "fibonacci") + + # Create FunctionToOptimize + func = FunctionToOptimize( + function_name=fib_func_info.function_name, + file_path=fib_func_info.file_path, + parents=[FunctionParent(name=p.name, type=p.type) for p in fib_func_info.parents], + starting_line=fib_func_info.starting_line, + ending_line=fib_func_info.ending_line, + language=fib_func_info.language, + ) + + # Verify language + assert func.language == "typescript" + + # Create test config + test_config = TestConfig( + tests_root=vitest_project / "tests", + tests_project_rootdir=vitest_project, + project_root_path=vitest_project, + pytest_cmd="vitest", + test_framework="vitest", + ) + + # Create optimizer + func_optimizer = FunctionOptimizer( + function_to_optimize=func, + test_cfg=test_config, + aiservice_client=MagicMock(), + ) + + # Get code context - this should work + result = func_optimizer.get_code_optimization_context() + context = result.unwrap() + + assert context is not None + assert context.read_writable_code.language == "typescript" + + +class TestTimingMarkerParsing: + """Tests for parsing JavaScript timing markers from test output. + + Note: Timing marker parsing is handled in codeflash/verification/parse_test_output.py, + which uses a unified parser for all languages. These tests verify the marker format + is correctly recognized. + """ + + def test_timing_marker_format(self): + """Test that JavaScript timing markers follow the expected format.""" + skip_if_js_not_supported() + import re + + # The marker format used by codeflash for JavaScript + # Start marker: !$######{tag}######$! + # End marker: !######{tag}:{duration}######! + start_pattern = r'!\$######(.+?)######\$!' + end_pattern = r'!######(.+?):(\d+)######!' + + start_marker = "!$######test/math.test.ts:TestMath.test_add:add:1:0_0######$!" + end_marker = "!######test/math.test.ts:TestMath.test_add:add:1:0_0:12345######!" + + start_match = re.match(start_pattern, start_marker) + end_match = re.match(end_pattern, end_marker) + + assert start_match is not None + assert end_match is not None + assert start_match.group(1) == "test/math.test.ts:TestMath.test_add:add:1:0_0" + assert end_match.group(1) == "test/math.test.ts:TestMath.test_add:add:1:0_0" + assert end_match.group(2) == "12345" + + def test_timing_marker_components(self): + """Test parsing components from timing marker tag.""" + skip_if_js_not_supported() + + # Tag format: {module}:{class}.{test}:{function}:{loop_index}:{invocation_id} + tag = "test/math.test.ts:TestMath.test_add:add:1:0_0" + parts = tag.split(":") + + assert len(parts) == 5 + assert parts[0] == "test/math.test.ts" # module/file + assert parts[1] == "TestMath.test_add" # class.test + assert parts[2] == "add" # function being tested + assert parts[3] == "1" # loop index + assert parts[4] == "0_0" # invocation id + + +class TestJavaScriptTestResultParsing: + """Tests for parsing JavaScript test results from JUnit XML.""" + + def test_parse_vitest_junit_xml(self, tmp_path): + """Test parsing Vitest JUnit XML output.""" + skip_if_js_not_supported() + + # Create sample JUnit XML + junit_xml = tmp_path / "junit.xml" + junit_xml.write_text(""" + + + + + + + + +""") + + # Parse the XML + import xml.etree.ElementTree as ET + tree = ET.parse(junit_xml) + root = tree.getroot() + + # Verify structure + testsuites = root if root.tag == "testsuites" else root.find("testsuites") + assert testsuites is not None + + testsuite = testsuites.find("testsuite") if testsuites is not None else root.find("testsuite") + assert testsuite is not None + + testcases = testsuite.findall("testcase") + assert len(testcases) == 2 + + def test_parse_jest_junit_xml(self, tmp_path): + """Test parsing Jest JUnit XML output.""" + skip_if_js_not_supported() + + # Create sample JUnit XML from jest-junit + junit_xml = tmp_path / "junit.xml" + junit_xml.write_text(""" + + + + + + + + +""") + + # Parse the XML + import xml.etree.ElementTree as ET + tree = ET.parse(junit_xml) + root = tree.getroot() + + # Verify structure + testsuites = root if root.tag == "testsuites" else root.find("testsuites") + testsuite = testsuites.find("testsuite") if testsuites is not None else root.find("testsuite") + assert testsuite is not None + + testcases = testsuite.findall("testcase") + assert len(testcases) == 2 diff --git a/tests/test_languages/test_javascript_support.py b/tests/test_languages/test_javascript_support.py index 887e07b98..7a6868a66 100644 --- a/tests/test_languages/test_javascript_support.py +++ b/tests/test_languages/test_javascript_support.py @@ -654,7 +654,7 @@ def test_find_jest_tests(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1607,3 +1607,97 @@ class MathUtils { f"Replacement result does not match expected.\nExpected:\n{expected_result}\n\nGot:\n{result}" ) assert js_support.validate_syntax(result) is True + + +class TestTypeScriptSyntaxValidation: + """Tests for TypeScript-specific syntax validation. + + These tests ensure that TypeScript code is validated with the TypeScript parser, + not the JavaScript parser. This is important because TypeScript has syntax that + is invalid in JavaScript (e.g., type assertions, type annotations). + """ + + def test_typescript_type_assertion_valid_in_ts(self): + """TypeScript type assertions should be valid in TypeScript.""" + from codeflash.languages.javascript.support import TypeScriptSupport + + ts_support = TypeScriptSupport() + + # Type assertions are TypeScript-specific + ts_code = """ +const value = 4.9 as unknown as number; +const str = "hello" as string; +""" + assert ts_support.validate_syntax(ts_code) is True + + def test_typescript_type_assertion_invalid_in_js(self, js_support): + """TypeScript type assertions should be invalid in JavaScript.""" + # This is the code pattern that caused the backend error + ts_code = """ +const value = 4.9 as unknown as number; +""" + # JavaScript parser should reject TypeScript syntax + assert js_support.validate_syntax(ts_code) is False + + def test_typescript_interface_valid_in_ts(self): + """TypeScript interfaces should be valid in TypeScript.""" + from codeflash.languages.javascript.support import TypeScriptSupport + + ts_support = TypeScriptSupport() + + ts_code = """ +interface User { + name: string; + age: number; +} +""" + assert ts_support.validate_syntax(ts_code) is True + + def test_typescript_interface_invalid_in_js(self, js_support): + """TypeScript interfaces should be invalid in JavaScript.""" + ts_code = """ +interface User { + name: string; + age: number; +} +""" + # JavaScript parser should reject TypeScript interface syntax + assert js_support.validate_syntax(ts_code) is False + + def test_typescript_generic_function_valid_in_ts(self): + """TypeScript generics should be valid in TypeScript.""" + from codeflash.languages.javascript.support import TypeScriptSupport + + ts_support = TypeScriptSupport() + + ts_code = """ +function identity(arg: T): T { + return arg; +} +""" + assert ts_support.validate_syntax(ts_code) is True + + def test_typescript_generic_function_invalid_in_js(self, js_support): + """TypeScript generics should be invalid in JavaScript.""" + ts_code = """ +function identity(arg: T): T { + return arg; +} +""" + assert js_support.validate_syntax(ts_code) is False + + def test_language_property_is_typescript(self): + """TypeScriptSupport should report typescript as language.""" + from codeflash.languages.base import Language + from codeflash.languages.javascript.support import TypeScriptSupport + + ts_support = TypeScriptSupport() + assert ts_support.language == Language.TYPESCRIPT + assert str(ts_support.language) == "typescript" + + def test_language_property_is_javascript(self, js_support): + """JavaScriptSupport should report javascript as language.""" + from codeflash.languages.base import Language + + assert js_support.language == Language.JAVASCRIPT + assert str(js_support.language) == "javascript" diff --git a/tests/test_languages/test_javascript_test_discovery.py b/tests/test_languages/test_javascript_test_discovery.py index 9166b589e..473bd330e 100644 --- a/tests/test_languages/test_javascript_test_discovery.py +++ b/tests/test_languages/test_javascript_test_discovery.py @@ -627,7 +627,7 @@ def test_find_basic_tests(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -652,7 +652,7 @@ def test_find_describe_blocks(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -676,7 +676,7 @@ def test_find_nested_describe_blocks(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -700,7 +700,7 @@ def test_find_tests_with_skip(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -721,7 +721,7 @@ def test_find_tests_with_only(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -739,7 +739,7 @@ def test_find_tests_with_single_quotes(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -758,7 +758,7 @@ def test_find_tests_with_double_quotes(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -774,7 +774,7 @@ def test_find_tests_empty_file(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1020,7 +1020,7 @@ def test_unicode_in_test_names(self, js_support): file_path = Path(f.name) source = file_path.read_text(encoding="utf-8") - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1048,7 +1048,7 @@ def test_find_test_each_array(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1074,7 +1074,7 @@ def test_find_describe_each(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1099,7 +1099,7 @@ def test_find_it_each(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1457,7 +1457,7 @@ def test_dynamic_test_names(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1485,7 +1485,7 @@ def test_conditional_tests(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1509,7 +1509,7 @@ def test_test_with_timeout(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1532,7 +1532,7 @@ def test_todo_tests(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1555,7 +1555,7 @@ def test_concurrent_tests(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1654,7 +1654,7 @@ def test_mocha_bdd_style(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) @@ -1685,7 +1685,7 @@ def test_context_block(self, js_support): file_path = Path(f.name) source = file_path.read_text() - from codeflash.languages.treesitter_utils import get_analyzer_for_file + from codeflash.languages.javascript.treesitter import get_analyzer_for_file analyzer = get_analyzer_for_file(file_path) test_names = js_support._find_jest_tests(source, analyzer) diff --git a/tests/test_languages/test_js_code_replacer.py b/tests/test_languages/test_js_code_replacer.py index 9cb53cab3..c5b2cc001 100644 --- a/tests/test_languages/test_js_code_replacer.py +++ b/tests/test_languages/test_js_code_replacer.py @@ -434,8 +434,8 @@ def test_no_conversion_when_project_root_is_none(self): # Should be converted to ESM assert "import x from './module';" in result - def test_mixed_code_not_converted(self, tmp_path): - """Test that mixed CJS/ESM code is NOT converted (already has both).""" + def test_mixed_code_converted_to_esm(self, tmp_path): + """Test that mixed CJS/ESM code has require converted to import when targeting ESM.""" package_json = tmp_path / "package.json" package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}') @@ -447,10 +447,18 @@ def test_mixed_code_not_converted(self, tmp_path): return existing() + helper(); } """ - # Mixed code has both import and require, so no conversion + expected = """\ +import { existing } from './module.js'; +import { helper } from './helpers'; + +function process() { + return existing() + helper(); +} +""" + # Mixed code should have require converted to import for ESM target result = ensure_module_system_compatibility(mixed_code, ModuleSystem.ES_MODULE, tmp_path) - assert result == mixed_code, "Mixed code should not be converted" + assert result == expected, "require should be converted to import for ESM target" def test_pure_esm_unchanged_for_esm_target(self, tmp_path): """Test that pure ESM code is unchanged when targeting ESM.""" diff --git a/tests/test_languages/test_registry.py b/tests/test_languages/test_registry.py index 4dbd1848b..fe844c38f 100644 --- a/tests/test_languages/test_registry.py +++ b/tests/test_languages/test_registry.py @@ -271,11 +271,14 @@ def test_clear_registry_removes_everything(self): # Now Python should not be supported assert not is_language_supported(Language.PYTHON) - # Re-register by importing + # Re-register all languages by importing from codeflash.languages.python.support import PythonSupport + from codeflash.languages.javascript.support import JavaScriptSupport, TypeScriptSupport # Need to manually register since decorator already ran register_language(PythonSupport) + register_language(JavaScriptSupport) + register_language(TypeScriptSupport) # Should be supported again assert is_language_supported(Language.PYTHON) diff --git a/tests/test_languages/test_treesitter_utils.py b/tests/test_languages/test_treesitter_utils.py index e5e776a11..15dd1219b 100644 --- a/tests/test_languages/test_treesitter_utils.py +++ b/tests/test_languages/test_treesitter_utils.py @@ -8,7 +8,7 @@ import pytest -from codeflash.languages.treesitter_utils import TreeSitterAnalyzer, TreeSitterLanguage, get_analyzer_for_file +from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage, get_analyzer_for_file class TestTreeSitterLanguage: @@ -545,3 +545,279 @@ def test_find_generic_function(self, ts_analyzer): assert len(functions) == 1 assert functions[0].name == "identity" + + +class TestExportConstArrowFunctions: + """Tests for export const arrow function pattern - Issue #10. + + Modern TypeScript codebases commonly use: + - export const slugify = (str: string) => { return s; } + - export const uniqueBy = (array: T[]) => { ... } + + These must be correctly recognized as optimizable functions. + """ + + @pytest.fixture + def ts_analyzer(self): + """Create a TypeScript analyzer.""" + return TreeSitterAnalyzer(TreeSitterLanguage.TYPESCRIPT) + + def test_export_const_arrow_function_basic(self, ts_analyzer): + """Test finding export const arrow function (basic pattern).""" + code = """export const slugify = (str: string) => { + return str.toLowerCase(); +};""" + functions = ts_analyzer.find_functions(code) + + assert len(functions) == 1 + assert functions[0].name == "slugify" + assert functions[0].is_arrow is True + assert ts_analyzer.has_return_statement(functions[0], code) is True + + def test_export_const_arrow_function_optional_param(self, ts_analyzer): + """Test finding export const arrow function with optional parameter.""" + code = """export const slugify = (str: string, forDisplayingInput?: boolean) => { + if (!str) { + return ""; + } + const s = str.toLowerCase(); + return forDisplayingInput ? s : s.replace(/-+$/, ""); +};""" + functions = ts_analyzer.find_functions(code) + + assert len(functions) == 1 + assert functions[0].name == "slugify" + assert functions[0].is_arrow is True + assert ts_analyzer.has_return_statement(functions[0], code) is True + + def test_export_const_generic_arrow_function(self, ts_analyzer): + """Test finding export const arrow function with generics.""" + code = """export const uniqueBy = (array: T[], keys: (keyof T)[]) => { + return array.filter( + (item, index, self) => index === self.findIndex((t) => keys.every((key) => t[key] === item[key])) + ); +};""" + functions = ts_analyzer.find_functions(code) + + # Should find uniqueBy, and possibly the inner arrow functions + uniqueBy = next((f for f in functions if f.name == "uniqueBy"), None) + assert uniqueBy is not None + assert uniqueBy.is_arrow is True + assert ts_analyzer.has_return_statement(uniqueBy, code) is True + + def test_export_const_arrow_function_is_exported(self, ts_analyzer): + """Test that export const arrow functions are recognized as exported.""" + code = """export const slugify = (str: string) => { + return str.toLowerCase(); +};""" + + # Check is_function_exported + is_exported, export_name = ts_analyzer.is_function_exported(code, "slugify") + assert is_exported is True + assert export_name == "slugify" + + def test_export_const_with_default_export(self, ts_analyzer): + """Test export const with separate default export.""" + code = """export const slugify = (str: string) => { + return str.toLowerCase(); +}; + +export default slugify;""" + + functions = ts_analyzer.find_functions(code) + assert len(functions) == 1 + assert functions[0].name == "slugify" + + # Should be exported both ways + is_named, named_name = ts_analyzer.is_function_exported(code, "slugify") + assert is_named is True + + def test_multiple_export_const_functions(self, ts_analyzer): + """Test multiple export const arrow functions in same file.""" + code = """export const notUndefined = (val: T | undefined): val is T => Boolean(val); + +export const uniqueBy = (array: T[], keys: (keyof T)[]) => { + return array.filter( + (item, index, self) => index === self.findIndex((t) => keys.every((key) => t[key] === item[key])) + ); +};""" + + functions = ts_analyzer.find_functions(code) + + # Find the top-level exported functions + names = {f.name for f in functions if f.parent_function is None} + assert "notUndefined" in names + assert "uniqueBy" in names + + def test_export_const_arrow_with_implicit_return(self, ts_analyzer): + """Test export const arrow function with implicit return.""" + code = """export const double = (n: number) => n * 2;""" + + functions = ts_analyzer.find_functions(code) + + assert len(functions) == 1 + assert functions[0].name == "double" + assert functions[0].is_arrow is True + assert ts_analyzer.has_return_statement(functions[0], code) is True + + def test_export_const_async_arrow_function(self, ts_analyzer): + """Test export const async arrow function.""" + code = """export const fetchData = async (url: string) => { + const response = await fetch(url); + return response.json(); +};""" + + functions = ts_analyzer.find_functions(code) + + assert len(functions) == 1 + assert functions[0].name == "fetchData" + assert functions[0].is_arrow is True + assert functions[0].is_async is True + assert ts_analyzer.has_return_statement(functions[0], code) is True + + def test_non_exported_const_not_exported(self, ts_analyzer): + """Test that non-exported const functions are not marked as exported.""" + code = """const privateFunc = (x: number) => { + return x * 2; +}; + +export const publicFunc = (x: number) => { + return privateFunc(x); +};""" + + # privateFunc should not be exported + is_private_exported, _ = ts_analyzer.is_function_exported(code, "privateFunc") + assert is_private_exported is False + + # publicFunc should be exported + is_public_exported, name = ts_analyzer.is_function_exported(code, "publicFunc") + assert is_public_exported is True + assert name == "publicFunc" + + +class TestWrappedDefaultExports: + """Tests for wrapped default export pattern - Issue #9. + + Handles patterns like: + - export default curry(traverseEntity) + - export default compose(fn1, fn2) + - export default wrapper(myFunc) + + These must be correctly recognized so the wrapped function is exportable. + """ + + @pytest.fixture + def ts_analyzer(self): + """Create a TypeScript analyzer.""" + return TreeSitterAnalyzer(TreeSitterLanguage.TYPESCRIPT) + + def test_curry_wrapped_export(self, ts_analyzer): + """Test export default curry(fn) pattern.""" + code = """import { curry } from 'lodash/fp'; + +const traverseEntity = async (visitor, options, entity) => { + return entity; +}; + +export default curry(traverseEntity);""" + + # Check exports parsing + exports = ts_analyzer.find_exports(code) + assert len(exports) == 1 + assert exports[0].default_export == "default" + assert exports[0].wrapped_default_args == ["traverseEntity"] + + # Check is_function_exported + is_exported, export_name = ts_analyzer.is_function_exported(code, "traverseEntity") + assert is_exported is True + assert export_name == "default" + + def test_compose_wrapped_export(self, ts_analyzer): + """Test export default compose(fn1, fn2) pattern with multiple args.""" + code = """import { compose } from 'lodash/fp'; + +function validateInput(data) { return data; } +function processData(data) { return data; } + +export default compose(validateInput, processData);""" + + exports = ts_analyzer.find_exports(code) + assert len(exports) == 1 + assert exports[0].wrapped_default_args == ["validateInput", "processData"] + + # Both functions should be recognized as exported + is_exported1, _ = ts_analyzer.is_function_exported(code, "validateInput") + is_exported2, _ = ts_analyzer.is_function_exported(code, "processData") + assert is_exported1 is True + assert is_exported2 is True + + def test_nested_wrapper_export(self, ts_analyzer): + """Test nested wrapper: export default compose(curry(fn)).""" + code = """export default compose(curry(myFunc));""" + + exports = ts_analyzer.find_exports(code) + assert len(exports) == 1 + assert "myFunc" in exports[0].wrapped_default_args + + is_exported, _ = ts_analyzer.is_function_exported(code, "myFunc") + assert is_exported is True + + def test_generic_wrapper_export(self, ts_analyzer): + """Test generic wrapper function.""" + code = """const myFunction = (x: number) => x * 2; + +export default someWrapper(myFunction);""" + + is_exported, export_name = ts_analyzer.is_function_exported(code, "myFunction") + assert is_exported is True + assert export_name == "default" + + def test_non_wrapped_function_not_exported(self, ts_analyzer): + """Test that functions not in the wrapper call are not exported.""" + code = """const helper = (x: number) => x + 1; +const main = (x: number) => helper(x) * 2; + +export default curry(main);""" + + # main is wrapped, so it's exported + is_main_exported, _ = ts_analyzer.is_function_exported(code, "main") + assert is_main_exported is True + + # helper is NOT in the wrapper call, so not exported + is_helper_exported, _ = ts_analyzer.is_function_exported(code, "helper") + assert is_helper_exported is False + + def test_direct_default_export_still_works(self, ts_analyzer): + """Test that direct default exports still work.""" + code = """function myFunc() { return 1; } +export default myFunc;""" + + is_exported, export_name = ts_analyzer.is_function_exported(code, "myFunc") + assert is_exported is True + assert export_name == "default" + + def test_strapi_traverse_entity_pattern(self, ts_analyzer): + """Test the exact strapi pattern that was failing.""" + code = """import { curry } from 'lodash/fp'; + +const traverseEntity = async (visitor: Visitor, options: TraverseOptions, entity: Data) => { + const { path = { raw: null }, schema, getModel } = options; + // ... implementation + return copy; +}; + +const createVisitorUtils = ({ data }: { data: Data }) => ({ + remove(key: string) { delete data[key]; }, + set(key: string, value: Data) { data[key] = value; }, +}); + +export default curry(traverseEntity);""" + + # traverseEntity should be recognized as exported + is_exported, export_name = ts_analyzer.is_function_exported(code, "traverseEntity") + assert is_exported is True + assert export_name == "default" + + # createVisitorUtils is NOT wrapped, so not exported via default + is_utils_exported, _ = ts_analyzer.is_function_exported(code, "createVisitorUtils") + assert is_utils_exported is False diff --git a/tests/test_languages/test_typescript_e2e.py b/tests/test_languages/test_typescript_e2e.py new file mode 100644 index 000000000..199094a1d --- /dev/null +++ b/tests/test_languages/test_typescript_e2e.py @@ -0,0 +1,446 @@ +"""End-to-end integration tests for TypeScript pipeline. + +Tests the full optimization pipeline for TypeScript: +- Function discovery +- Code context extraction +- Test discovery +- Code replacement +- Syntax validation with TypeScript parser (not JavaScript) + +This is the TypeScript equivalent of test_javascript_e2e.py. +Ensures parity between JavaScript and TypeScript support. +""" + +import tempfile +from pathlib import Path + +import pytest + +from codeflash.languages.base import Language + + +def skip_if_ts_not_supported(): + """Skip test if TypeScript language is not supported.""" + try: + from codeflash.languages import get_language_support + + get_language_support(Language.TYPESCRIPT) + except Exception as e: + pytest.skip(f"TypeScript language support not available: {e}") + + +class TestTypeScriptFunctionDiscovery: + """Tests for TypeScript function discovery in the main pipeline.""" + + @pytest.fixture + def ts_project_dir(self): + """Get the TypeScript sample project directory.""" + project_root = Path(__file__).parent.parent.parent + ts_dir = project_root / "code_to_optimize" / "js" / "code_to_optimize_vitest" + if not ts_dir.exists(): + pytest.skip("code_to_optimize_vitest directory not found") + return ts_dir + + def test_discover_functions_in_typescript_file(self, ts_project_dir): + """Test discovering functions in a TypeScript file.""" + skip_if_ts_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + + fib_file = ts_project_dir / "fibonacci.ts" + if not fib_file.exists(): + pytest.skip("fibonacci.ts not found") + + functions = find_all_functions_in_file(fib_file) + + assert fib_file in functions + func_list = functions[fib_file] + + func_names = {f.function_name for f in func_list} + assert "fibonacci" in func_names + + # Critical: Verify language is "typescript", not "javascript" + for func in func_list: + assert func.language == "typescript", \ + f"Function {func.function_name} should have language='typescript', got '{func.language}'" + + def test_discover_functions_with_type_annotations(self): + """Test discovering TypeScript functions with type annotations.""" + skip_if_ts_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + + with tempfile.NamedTemporaryFile(suffix=".ts", mode="w", delete=False) as f: + f.write(""" +export function add(a: number, b: number): number { + return a + b; +} + +export function greet(name: string): string { + return `Hello, \${name}!`; +} + +interface User { + name: string; + age: number; +} + +export function getUserAge(user: User): number { + return user.age; +} +""") + f.flush() + file_path = Path(f.name) + + functions = find_all_functions_in_file(file_path) + + assert len(functions.get(file_path, [])) == 3 + + for func in functions[file_path]: + assert func.language == "typescript" + + def test_get_typescript_files(self, ts_project_dir): + """Test getting TypeScript files from directory.""" + skip_if_ts_not_supported() + from codeflash.discovery.functions_to_optimize import get_files_for_language + + files = get_files_for_language(ts_project_dir, Language.TYPESCRIPT) + + ts_files = [f for f in files if f.suffix == ".ts" and "test" not in f.name] + assert len(ts_files) >= 1 + + +class TestTypeScriptCodeContext: + """Tests for TypeScript code context extraction.""" + + @pytest.fixture + def ts_project_dir(self): + """Get the TypeScript sample project directory.""" + project_root = Path(__file__).parent.parent.parent + ts_dir = project_root / "code_to_optimize" / "js" / "code_to_optimize_vitest" + if not ts_dir.exists(): + pytest.skip("code_to_optimize_vitest directory not found") + return ts_dir + + def test_extract_code_context_for_typescript(self, ts_project_dir): + """Test extracting code context for a TypeScript function.""" + skip_if_ts_not_supported() + from codeflash.context.code_context_extractor import get_code_optimization_context + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + from codeflash.languages import current as lang_current + + lang_current._current_language = Language.TYPESCRIPT + + fib_file = ts_project_dir / "fibonacci.ts" + if not fib_file.exists(): + pytest.skip("fibonacci.ts not found") + + functions = find_all_functions_in_file(fib_file) + func_list = functions[fib_file] + + fib_func = next((f for f in func_list if f.function_name == "fibonacci"), None) + assert fib_func is not None + + context = get_code_optimization_context(fib_func, ts_project_dir) + + assert context.read_writable_code is not None + # Critical: language should be "typescript", not "javascript" + assert context.read_writable_code.language == "typescript" + assert len(context.read_writable_code.code_strings) > 0 + + +class TestTypeScriptCodeReplacement: + """Tests for TypeScript code replacement.""" + + def test_replace_function_in_typescript_file(self): + """Test replacing a function in a TypeScript file.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + from codeflash.languages.base import FunctionInfo + + original_source = """ +function add(a: number, b: number): number { + return a + b; +} + +function multiply(a: number, b: number): number { + return a * b; +} +""" + + new_function = """function add(a: number, b: number): number { + // Optimized version + return a + b; +}""" + + ts_support = get_language_support(Language.TYPESCRIPT) + + func_info = FunctionInfo( + function_name="add", + file_path=Path("/tmp/test.ts"), + starting_line=2, + ending_line=4, + language="typescript" + ) + + result = ts_support.replace_function(original_source, func_info, new_function) + + expected_result = """ +function add(a: number, b: number): number { + // Optimized version + return a + b; +} + +function multiply(a: number, b: number): number { + return a * b; +} +""" + assert result == expected_result + + def test_replace_function_preserves_types(self): + """Test that replacing a function preserves TypeScript type annotations.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + from codeflash.languages.base import FunctionInfo + + original_source = """ +interface Config { + timeout: number; + retries: number; +} + +function processConfig(config: Config): string { + return `timeout=\${config.timeout}, retries=\${config.retries}`; +} +""" + + new_function = """function processConfig(config: Config): string { + // Optimized with template caching + const { timeout, retries } = config; + return `timeout=\${timeout}, retries=\${retries}`; +}""" + + ts_support = get_language_support(Language.TYPESCRIPT) + + func_info = FunctionInfo( + function_name="processConfig", + file_path=Path("/tmp/test.ts"), + starting_line=7, + ending_line=9, + language="typescript" + ) + + result = ts_support.replace_function(original_source, func_info, new_function) + + # Verify type annotations are preserved + assert "config: Config" in result + assert ": string" in result + assert "interface Config" in result + + +class TestTypeScriptTestDiscovery: + """Tests for TypeScript test discovery.""" + + @pytest.fixture + def ts_project_dir(self): + """Get the TypeScript sample project directory.""" + project_root = Path(__file__).parent.parent.parent + ts_dir = project_root / "code_to_optimize" / "js" / "code_to_optimize_vitest" + if not ts_dir.exists(): + pytest.skip("code_to_optimize_vitest directory not found") + return ts_dir + + def test_discover_vitest_tests_for_typescript(self, ts_project_dir): + """Test discovering Vitest tests for TypeScript functions.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + from codeflash.languages.base import FunctionInfo + + ts_support = get_language_support(Language.TYPESCRIPT) + test_root = ts_project_dir / "tests" + + if not test_root.exists(): + pytest.skip("tests directory not found") + + fib_file = ts_project_dir / "fibonacci.ts" + func_info = FunctionInfo( + function_name="fibonacci", + file_path=fib_file, + starting_line=1, + ending_line=7, + language="typescript" + ) + + tests = ts_support.discover_tests(test_root, [func_info]) + + # Should find tests for the fibonacci function + assert func_info.qualified_name in tests or len(tests) > 0 + + +class TestTypeScriptPipelineIntegration: + """Integration tests for the full TypeScript pipeline.""" + + def test_function_to_optimize_has_correct_fields(self): + """Test that FunctionToOptimize from TypeScript has all required fields.""" + skip_if_ts_not_supported() + from codeflash.discovery.functions_to_optimize import find_all_functions_in_file + + with tempfile.NamedTemporaryFile(suffix=".ts", mode="w", delete=False) as f: + f.write(""" +class Calculator { + add(a: number, b: number): number { + return a + b; + } + + subtract(a: number, b: number): number { + return a - b; + } +} + +function standalone(x: number): number { + return x * 2; +} +""") + f.flush() + file_path = Path(f.name) + + functions = find_all_functions_in_file(file_path) + + assert len(functions.get(file_path, [])) >= 3 + + standalone_fn = next((fn for fn in functions[file_path] if fn.function_name == "standalone"), None) + assert standalone_fn is not None + assert standalone_fn.language == "typescript" + assert len(standalone_fn.parents) == 0 + + add_fn = next((fn for fn in functions[file_path] if fn.function_name == "add"), None) + assert add_fn is not None + assert add_fn.language == "typescript" + assert len(add_fn.parents) == 1 + assert add_fn.parents[0].name == "Calculator" + + def test_code_strings_markdown_uses_typescript_tag(self): + """Test that CodeStringsMarkdown uses typescript for code blocks.""" + from codeflash.models.models import CodeString, CodeStringsMarkdown + + code_strings = CodeStringsMarkdown( + code_strings=[ + CodeString( + code="function add(a: number, b: number): number { return a + b; }", + file_path=Path("test.ts"), + language="typescript" + ) + ], + language="typescript", + ) + + markdown = code_strings.markdown + assert "```typescript" in markdown + + +class TestTypeScriptSyntaxValidation: + """Tests for TypeScript-specific syntax validation. + + These tests ensure TypeScript code is validated with the TypeScript parser, + not the JavaScript parser. This was the root cause of production issues. + """ + + def test_typescript_type_assertion_valid(self): + """TypeScript type assertions should be valid.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + + ts_support = get_language_support(Language.TYPESCRIPT) + + # This is TypeScript-specific syntax that should pass + code = "const value = 4.9 as unknown as number;" + assert ts_support.validate_syntax(code) is True + + def test_typescript_type_assertion_invalid_in_javascript(self): + """TypeScript type assertions should be INVALID in JavaScript. + + This test would have caught the production bug where TypeScript code + was being validated with the JavaScript parser. + """ + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + + js_support = get_language_support(Language.JAVASCRIPT) + + # This TypeScript syntax should FAIL JavaScript validation + code = "const value = 4.9 as unknown as number;" + assert js_support.validate_syntax(code) is False + + def test_typescript_interface_valid(self): + """TypeScript interfaces should be valid.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + + ts_support = get_language_support(Language.TYPESCRIPT) + + code = """ +interface User { + name: string; + age: number; +} +""" + assert ts_support.validate_syntax(code) is True + + def test_typescript_interface_invalid_in_javascript(self): + """TypeScript interfaces should be INVALID in JavaScript.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + + js_support = get_language_support(Language.JAVASCRIPT) + + code = """ +interface User { + name: string; + age: number; +} +""" + assert js_support.validate_syntax(code) is False + + def test_typescript_generic_function_valid(self): + """TypeScript generics should be valid.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + + ts_support = get_language_support(Language.TYPESCRIPT) + + code = "function identity(arg: T): T { return arg; }" + assert ts_support.validate_syntax(code) is True + + def test_typescript_generic_function_invalid_in_javascript(self): + """TypeScript generics should be INVALID in JavaScript.""" + skip_if_ts_not_supported() + from codeflash.languages import get_language_support + + js_support = get_language_support(Language.JAVASCRIPT) + + code = "function identity(arg: T): T { return arg; }" + assert js_support.validate_syntax(code) is False + + +class TestTypeScriptCodeStringValidation: + """Tests for CodeString validation with TypeScript.""" + + def test_code_string_validates_typescript_with_typescript_parser(self): + """CodeString with language='typescript' should use TypeScript parser.""" + skip_if_ts_not_supported() + from codeflash.models.models import CodeString + + # TypeScript-specific syntax should pass when language='typescript' + ts_code = "const value = 4.9 as unknown as number;" + cs = CodeString(code=ts_code, language="typescript") + assert cs.code == ts_code + + def test_code_string_rejects_typescript_with_javascript_parser(self): + """CodeString with language='javascript' should reject TypeScript syntax.""" + skip_if_ts_not_supported() + from pydantic import ValidationError + + from codeflash.models.models import CodeString + + # TypeScript-specific syntax should FAIL when language='javascript' + ts_code = "const value = 4.9 as unknown as number;" + with pytest.raises(ValidationError): + CodeString(code=ts_code, language="javascript") diff --git a/tests/test_validate_javascript_code.py b/tests/test_validate_javascript_code.py new file mode 100644 index 000000000..5b56e36c5 --- /dev/null +++ b/tests/test_validate_javascript_code.py @@ -0,0 +1,134 @@ +"""Tests for JavaScript/TypeScript code validation in CodeString. + +These tests ensure that JavaScript and TypeScript code is validated correctly +using the appropriate syntax parser for each language. +""" + +import pytest +from pydantic import ValidationError + +from codeflash.api.aiservice import AiServiceClient +from codeflash.models.models import CodeString, OptimizedCandidateSource + + +class TestJavaScriptCodeValidation: + """Tests for JavaScript code validation.""" + + def test_valid_javascript_code(self): + """Valid JavaScript code should pass validation.""" + valid_code = "const x = 1;\nconst y = x + 2;\nconsole.log(y);" + cs = CodeString(code=valid_code, language="javascript") + assert cs.code == valid_code + + def test_invalid_javascript_code_syntax(self): + """Invalid JavaScript syntax should raise ValidationError.""" + invalid_code = "const x = 1;\nconsole.log(x" # Missing closing parenthesis + with pytest.raises(ValidationError) as exc_info: + CodeString(code=invalid_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + def test_javascript_empty_code(self): + """Empty code is syntactically valid.""" + empty_code = "" + cs = CodeString(code=empty_code, language="javascript") + assert cs.code == empty_code + + def test_javascript_arrow_function(self): + """Arrow functions should be valid JavaScript.""" + code = "const add = (a, b) => a + b;" + cs = CodeString(code=code, language="javascript") + assert cs.code == code + + +class TestTypeScriptCodeValidation: + """Tests for TypeScript code validation.""" + + def test_valid_typescript_code(self): + """Valid TypeScript code should pass validation.""" + valid_code = "const x: number = 1;\nconst y: number = x + 2;\nconsole.log(y);" + cs = CodeString(code=valid_code, language="typescript") + assert cs.code == valid_code + + def test_typescript_type_assertion(self): + """TypeScript type assertions should be valid.""" + code = "const value = 4.9 as unknown as number;" + cs = CodeString(code=code, language="typescript") + assert cs.code == code + + def test_typescript_interface(self): + """TypeScript interfaces should be valid.""" + code = "interface User { name: string; age: number; }" + cs = CodeString(code=code, language="typescript") + assert cs.code == code + + def test_typescript_generic_function(self): + """TypeScript generics should be valid.""" + code = "function identity(arg: T): T { return arg; }" + cs = CodeString(code=code, language="typescript") + assert cs.code == code + + def test_invalid_typescript_code_syntax(self): + """Invalid TypeScript syntax should raise ValidationError.""" + invalid_code = "const x: number = 1;\nconsole.log(x" # Missing closing parenthesis + with pytest.raises(ValidationError) as exc_info: + CodeString(code=invalid_code, language="typescript") + assert "Invalid Typescript code" in str(exc_info.value) + + def test_typescript_syntax_invalid_as_javascript(self): + """TypeScript-specific syntax should fail when validated as JavaScript.""" + ts_code = "const value = 4.9 as unknown as number;" + # Should pass as TypeScript + cs_ts = CodeString(code=ts_code, language="typescript") + assert cs_ts.code == ts_code + + # Should fail as JavaScript (type assertions are not valid JS) + with pytest.raises(ValidationError) as exc_info: + CodeString(code=ts_code, language="javascript") + assert "Invalid Javascript code" in str(exc_info.value) + + +class TestGeneratedCandidatesValidation: + """Tests for validation of generated optimization candidates.""" + + def test_javascript_generated_candidates_validation(self): + """JavaScript optimization candidates should be validated.""" + ai_service = AiServiceClient() + + # Invalid JavaScript code + invalid_code = """```javascript:file.js +const x = 1 +console.log(x +```""" + mock_candidates = [{"source_code": invalid_code, "explanation": "", "optimization_id": ""}] + candidates = ai_service._get_valid_candidates( + mock_candidates, OptimizedCandidateSource.OPTIMIZE, language="javascript" + ) + assert len(candidates) == 0 + + # Valid JavaScript code + valid_code = """```javascript:file.js +const x = 1; +console.log(x); +```""" + mock_candidates = [{"source_code": valid_code, "explanation": "", "optimization_id": ""}] + candidates = ai_service._get_valid_candidates( + mock_candidates, OptimizedCandidateSource.OPTIMIZE, language="javascript" + ) + assert len(candidates) == 1 + + def test_typescript_generated_candidates_validation(self): + """TypeScript optimization candidates should be validated.""" + ai_service = AiServiceClient() + + # TypeScript code with type assertions (valid TS, invalid JS) + ts_code = """```typescript:file.ts +const value = 4.9 as unknown as number; +console.log(value); +```""" + mock_candidates = [{"source_code": ts_code, "explanation": "", "optimization_id": ""}] + + # Should pass when validated as TypeScript + candidates = ai_service._get_valid_candidates( + mock_candidates, OptimizedCandidateSource.OPTIMIZE, language="typescript" + ) + assert len(candidates) == 1 diff --git a/uv.lock b/uv.lock index 926d8d04d..a55ae4db2 100644 --- a/uv.lock +++ b/uv.lock @@ -237,15 +237,15 @@ wheels = [ [[package]] name = "blessed" -version = "1.29.0" +version = "1.30.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinxed", marker = "sys_platform == 'win32'" }, { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/1f/ed37dbe0fd7f026cfbff0d968b924f1a53411e1c3a4639762467d3b06d24/blessed-1.29.0.tar.gz", hash = "sha256:4938cbfea8280885c853c0700850704aeacb25a97fca56de5e1e30ca63a0f1aa", size = 13950929, upload-time = "2026-02-01T15:26:38.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/19/e926a0dbbf93c7aeb15d4dfff0d0e3de02653b3ba540b687307d0819c1ff/blessed-1.30.0.tar.gz", hash = "sha256:4d547019d7b40fc5420ea2ba2bc180fdccc31d6715298e2b49ffa7b020d44667", size = 13948932, upload-time = "2026-02-06T19:40:23.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/47/566dcc08f8037511355a49220a3eecb795f955bb6145f419d5e60dbeaa00/blessed-1.29.0-py3-none-any.whl", hash = "sha256:cd5f339a308ac5e97d814c4bcbf7ce9723c465ca73c42744ea72646a0c653f6e", size = 100645, upload-time = "2026-02-01T15:26:35.632Z" }, + { url = "https://files.pythonhosted.org/packages/64/b0/8d87c7c8015ce8d4b2c5ee7a82a1d955f10138322c4f0cb387d7d2c1b2e7/blessed-1.30.0-py3-none-any.whl", hash = "sha256:4061a9f10dd22798716c2548ba36385af6a29d856c897f367c6ccc927e0b3a5a", size = 98399, upload-time = "2026-02-06T19:40:20.815Z" }, ] [[package]] @@ -427,7 +427,7 @@ dependencies = [ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "codeflash-benchmark" }, { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "coverage", version = "7.13.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "coverage", version = "7.13.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "crosshair-tool" }, { name = "dill" }, { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -448,7 +448,7 @@ dependencies = [ { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "posthog", version = "6.9.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "posthog", version = "7.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "posthog", version = "7.8.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pydantic" }, { name = "pygls" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -502,7 +502,7 @@ tests = [ { name = "eval-type-backport" }, { name = "jax", version = "0.4.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "jax", version = "0.6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "jax", version = "0.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jax", version = "0.9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numba", version = "0.60.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numba", version = "0.63.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -510,6 +510,8 @@ tests = [ { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "23.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyrsistent" }, { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, @@ -545,7 +547,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=1.10.1" }, { name = "pygls", specifier = ">=2.0.0,<3.0.0" }, { name = "pytest", specifier = ">=7.0.0" }, - { name = "pytest-asyncio", specifier = ">=1.2.0" }, + { name = "pytest-asyncio", specifier = ">=0.18.0" }, { name = "pytest-timeout", specifier = ">=2.1.0" }, { name = "rich", specifier = ">=13.8.1" }, { name = "sentry-sdk", specifier = ">=1.40.6,<3.0.0" }, @@ -589,6 +591,7 @@ tests = [ { name = "numba", specifier = ">=0.60.0" }, { name = "numpy", specifier = ">=2.0.2" }, { name = "pandas", specifier = ">=2.3.3" }, + { name = "pyarrow", specifier = ">=15.0.0" }, { name = "pyrsistent", specifier = ">=0.20.0" }, { name = "scipy", specifier = ">=1.13.1" }, { name = "tensorflow", specifier = ">=2.20.0" }, @@ -734,7 +737,7 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.3" +version = "7.13.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -751,99 +754,113 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/07/1c8099563a8a6c389a31c2d0aa1497cee86d6248bb4b9ba5e779215db9f9/coverage-7.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", size = 219143, upload-time = "2026-02-03T13:59:40.459Z" }, - { url = "https://files.pythonhosted.org/packages/69/39/a892d44af7aa092cab70e0cc5cdbba18eeccfe1d6930695dab1742eef9e9/coverage-7.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b", size = 219663, upload-time = "2026-02-03T13:59:41.951Z" }, - { url = "https://files.pythonhosted.org/packages/9a/25/9669dcf4c2bb4c3861469e6db20e52e8c11908cf53c14ec9b12e9fd4d602/coverage-7.13.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8", size = 246424, upload-time = "2026-02-03T13:59:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/f3/68/d9766c4e298aca62ea5d9543e1dd1e4e1439d7284815244d8b7db1840bfb/coverage-7.13.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0", size = 248228, upload-time = "2026-02-03T13:59:44.816Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e2/eea6cb4a4bd443741adf008d4cccec83a1f75401df59b6559aca2bdd9710/coverage-7.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6", size = 250103, upload-time = "2026-02-03T13:59:46.271Z" }, - { url = "https://files.pythonhosted.org/packages/db/77/664280ecd666c2191610842177e2fab9e5dbdeef97178e2078fed46a3d2c/coverage-7.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f", size = 247107, upload-time = "2026-02-03T13:59:48.53Z" }, - { url = "https://files.pythonhosted.org/packages/2b/df/2a672eab99e0d0eba52d8a63e47dc92245eee26954d1b2d3c8f7d372151f/coverage-7.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e", size = 248143, upload-time = "2026-02-03T13:59:50.027Z" }, - { url = "https://files.pythonhosted.org/packages/a5/dc/a104e7a87c13e57a358b8b9199a8955676e1703bb372d79722b54978ae45/coverage-7.13.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56", size = 246148, upload-time = "2026-02-03T13:59:52.025Z" }, - { url = "https://files.pythonhosted.org/packages/2b/89/e113d3a58dc20b03b7e59aed1e53ebc9ca6167f961876443e002b10e3ae9/coverage-7.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f", size = 246414, upload-time = "2026-02-03T13:59:53.859Z" }, - { url = "https://files.pythonhosted.org/packages/3f/60/a3fd0a6e8d89b488396019a2268b6a1f25ab56d6d18f3be50f35d77b47dc/coverage-7.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a", size = 247023, upload-time = "2026-02-03T13:59:55.454Z" }, - { url = "https://files.pythonhosted.org/packages/19/fa/de4840bb939dbb22ba0648a6d8069fa91c9cf3b3fca8b0d1df461e885b3d/coverage-7.13.3-cp310-cp310-win32.whl", hash = "sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be", size = 221751, upload-time = "2026-02-03T13:59:57.383Z" }, - { url = "https://files.pythonhosted.org/packages/de/87/233ff8b7ef62fb63f58c78623b50bef69681111e0c4d43504f422d88cda4/coverage-7.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b", size = 222686, upload-time = "2026-02-03T13:59:58.825Z" }, - { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, - { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, - { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, - { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, - { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, - { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, - { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, - { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, - { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, - { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, - { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, - { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, - { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, - { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, - { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, - { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, - { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, - { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, - { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, - { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, - { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, - { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, - { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, - { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, - { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, - { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, - { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, - { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, - { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, - { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, - { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, - { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, - { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, - { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, - { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, - { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, - { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, - { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, - { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, - { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, - { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, - { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, - { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, - { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, - { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, - { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" }, + { url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" }, + { url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" }, + { url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" }, + { url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" }, + { url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" }, + { url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] @@ -1017,7 +1034,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1093,7 +1110,7 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.1.0" +version = "2026.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -1110,9 +1127,9 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, ] [[package]] @@ -1163,73 +1180,73 @@ wheels = [ [[package]] name = "grpcio" -version = "1.76.0" +version = "1.78.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/17/ff4795dc9a34b6aee6ec379f1b66438a3789cd1315aac0cbab60d92f74b3/grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc", size = 5840037, upload-time = "2025-10-21T16:20:25.069Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ff/35f9b96e3fa2f12e1dcd58a4513a2e2294a001d64dec81677361b7040c9a/grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde", size = 11836482, upload-time = "2025-10-21T16:20:30.113Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1c/8374990f9545e99462caacea5413ed783014b3b66ace49e35c533f07507b/grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3", size = 6407178, upload-time = "2025-10-21T16:20:32.733Z" }, - { url = "https://files.pythonhosted.org/packages/1e/77/36fd7d7c75a6c12542c90a6d647a27935a1ecaad03e0ffdb7c42db6b04d2/grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990", size = 7075684, upload-time = "2025-10-21T16:20:35.435Z" }, - { url = "https://files.pythonhosted.org/packages/38/f7/e3cdb252492278e004722306c5a8935eae91e64ea11f0af3437a7de2e2b7/grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af", size = 6611133, upload-time = "2025-10-21T16:20:37.541Z" }, - { url = "https://files.pythonhosted.org/packages/7e/20/340db7af162ccd20a0893b5f3c4a5d676af7b71105517e62279b5b61d95a/grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2", size = 7195507, upload-time = "2025-10-21T16:20:39.643Z" }, - { url = "https://files.pythonhosted.org/packages/10/f0/b2160addc1487bd8fa4810857a27132fb4ce35c1b330c2f3ac45d697b106/grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6", size = 8160651, upload-time = "2025-10-21T16:20:42.492Z" }, - { url = "https://files.pythonhosted.org/packages/2c/2c/ac6f98aa113c6ef111b3f347854e99ebb7fb9d8f7bb3af1491d438f62af4/grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3", size = 7620568, upload-time = "2025-10-21T16:20:45.995Z" }, - { url = "https://files.pythonhosted.org/packages/90/84/7852f7e087285e3ac17a2703bc4129fafee52d77c6c82af97d905566857e/grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b", size = 3998879, upload-time = "2025-10-21T16:20:48.592Z" }, - { url = "https://files.pythonhosted.org/packages/10/30/d3d2adcbb6dd3ff59d6ac3df6ef830e02b437fb5c90990429fd180e52f30/grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b", size = 4706892, upload-time = "2025-10-21T16:20:50.697Z" }, - { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567, upload-time = "2025-10-21T16:20:52.829Z" }, - { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017, upload-time = "2025-10-21T16:20:56.705Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027, upload-time = "2025-10-21T16:20:59.3Z" }, - { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913, upload-time = "2025-10-21T16:21:01.645Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417, upload-time = "2025-10-21T16:21:03.844Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683, upload-time = "2025-10-21T16:21:06.195Z" }, - { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109, upload-time = "2025-10-21T16:21:08.498Z" }, - { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676, upload-time = "2025-10-21T16:21:10.693Z" }, - { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688, upload-time = "2025-10-21T16:21:12.746Z" }, - { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315, upload-time = "2025-10-21T16:21:15.26Z" }, - { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, - { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, - { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, - { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, - { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, - { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, - { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, - { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, - { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, - { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, - { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, - { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, - { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, - { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, - { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" }, - { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" }, - { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" }, - { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" }, - { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" }, - { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" }, - { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d5/301e71c7d22a5c7aabf1953dd1106987bd47f883377d528355f898a850f2/grpcio-1.76.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:8ebe63ee5f8fa4296b1b8cfc743f870d10e902ca18afc65c68cf46fd39bb0783", size = 5840371, upload-time = "2025-10-21T16:22:42.468Z" }, - { url = "https://files.pythonhosted.org/packages/00/55/e3181adccff8808301dd9214b5e03c6db5a404b5ae8a6ec5768a5a65ed63/grpcio-1.76.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:3bf0f392c0b806905ed174dcd8bdd5e418a40d5567a05615a030a5aeddea692d", size = 11840384, upload-time = "2025-10-21T16:22:45.508Z" }, - { url = "https://files.pythonhosted.org/packages/65/36/db1dfe943bce7180f5b6d9be564366ca1024a005e914a1f10212c24a840b/grpcio-1.76.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b7604868b38c1bfd5cf72d768aedd7db41d78cb6a4a18585e33fb0f9f2363fd", size = 6408765, upload-time = "2025-10-21T16:22:48.761Z" }, - { url = "https://files.pythonhosted.org/packages/1e/79/a8452764aa4b5ca30a970e514ec2fc5cf75451571793f6b276b6807f67dc/grpcio-1.76.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e6d1db20594d9daba22f90da738b1a0441a7427552cc6e2e3d1297aeddc00378", size = 7076220, upload-time = "2025-10-21T16:22:51.546Z" }, - { url = "https://files.pythonhosted.org/packages/e0/61/4cca38c4e7bb3ac5a1e0be6cf700a4dd85c61cbd8a9c5e076c224967084e/grpcio-1.76.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d099566accf23d21037f18a2a63d323075bebace807742e4b0ac210971d4dd70", size = 6610195, upload-time = "2025-10-21T16:22:54.688Z" }, - { url = "https://files.pythonhosted.org/packages/54/3d/3f8bfae264c22c95fa702c35aa2a8105b754b4ace049c66a8b2230c97671/grpcio-1.76.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ebea5cc3aa8ea72e04df9913492f9a96d9348db876f9dda3ad729cfedf7ac416", size = 7193343, upload-time = "2025-10-21T16:22:57.434Z" }, - { url = "https://files.pythonhosted.org/packages/d1/cd/89f9254782b6cd94aa7c93fde370862877113b7189fb49900eaf9a706c82/grpcio-1.76.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0c37db8606c258e2ee0c56b78c62fc9dee0e901b5dbdcf816c2dd4ad652b8b0c", size = 8161922, upload-time = "2025-10-21T16:23:00.135Z" }, - { url = "https://files.pythonhosted.org/packages/af/e0/99eb899d7cb9c676afea70ab6d02a72a9e6ce24d0300f625773fafe6d547/grpcio-1.76.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ebebf83299b0cb1721a8859ea98f3a77811e35dce7609c5c963b9ad90728f886", size = 7617951, upload-time = "2025-10-21T16:23:03.68Z" }, - { url = "https://files.pythonhosted.org/packages/79/26/dca1b2bfaa9981cc28fa995730c80eedb0b86c912c30d1b676f08232e6ab/grpcio-1.76.0-cp39-cp39-win32.whl", hash = "sha256:0aaa82d0813fd4c8e589fac9b65d7dd88702555f702fb10417f96e2a2a6d4c0f", size = 3999306, upload-time = "2025-10-21T16:23:06.187Z" }, - { url = "https://files.pythonhosted.org/packages/de/d1/fb90564a981eedd3cd87dc6bfd7c249e8a515cfad1ed8e9af73be223cd3b/grpcio-1.76.0-cp39-cp39-win_amd64.whl", hash = "sha256:acab0277c40eff7143c2323190ea57b9ee5fd353d8190ee9652369fae735668a", size = 4708771, upload-time = "2025-10-21T16:23:08.902Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/a8/690a085b4d1fe066130de97a87de32c45062cf2ecd218df9675add895550/grpcio-1.78.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5", size = 5946986, upload-time = "2026-02-06T09:54:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/c7/1b/e5213c5c0ced9d2d92778d30529ad5bb2dcfb6c48c4e2d01b1f302d33d64/grpcio-1.78.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2", size = 11816533, upload-time = "2026-02-06T09:54:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/18/37/1ba32dccf0a324cc5ace744c44331e300b000a924bf14840f948c559ede7/grpcio-1.78.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d", size = 6519964, upload-time = "2026-02-06T09:54:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f5/c0e178721b818072f2e8b6fde13faaba942406c634009caf065121ce246b/grpcio-1.78.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb", size = 7198058, upload-time = "2026-02-06T09:54:42.389Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b2/40d43c91ae9cd667edc960135f9f08e58faa1576dc95af29f66ec912985f/grpcio-1.78.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7", size = 6727212, upload-time = "2026-02-06T09:54:44.91Z" }, + { url = "https://files.pythonhosted.org/packages/ed/88/9da42eed498f0efcfcd9156e48ae63c0cde3bea398a16c99fb5198c885b6/grpcio-1.78.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec", size = 7300845, upload-time = "2026-02-06T09:54:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/23/3f/1c66b7b1b19a8828890e37868411a6e6925df5a9030bfa87ab318f34095d/grpcio-1.78.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a", size = 8284605, upload-time = "2026-02-06T09:54:50.475Z" }, + { url = "https://files.pythonhosted.org/packages/94/c4/ca1bd87394f7b033e88525384b4d1e269e8424ab441ea2fba1a0c5b50986/grpcio-1.78.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813", size = 7726672, upload-time = "2026-02-06T09:54:53.11Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/f16e487d4cc65ccaf670f6ebdd1a17566b965c74fc3d93999d3b2821e052/grpcio-1.78.0-cp310-cp310-win32.whl", hash = "sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de", size = 4076715, upload-time = "2026-02-06T09:54:55.549Z" }, + { url = "https://files.pythonhosted.org/packages/2a/32/4ce60d94e242725fd3bcc5673c04502c82a8e87b21ea411a63992dc39f8f/grpcio-1.78.0-cp310-cp310-win_amd64.whl", hash = "sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf", size = 4799157, upload-time = "2026-02-06T09:54:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, + { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, + { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, + { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, + { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, + { url = "https://files.pythonhosted.org/packages/58/6c/40a4bba2c753ea8eeb8d776a31e9c54f4e506edf36db93a3db5456725294/grpcio-1.78.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:86f85dd7c947baa707078a236288a289044836d4b640962018ceb9cd1f899af5", size = 5947902, upload-time = "2026-02-06T09:56:48.469Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4c/ed7664a37a7008be41204c77e0d88bbc4ac531bcf0c27668cd066f9ff6e2/grpcio-1.78.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:de8cb00d1483a412a06394b8303feec5dcb3b55f81d83aa216dbb6a0b86a94f5", size = 11824772, upload-time = "2026-02-06T09:56:51.264Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5b/45a5c23ba3c4a0f51352366d9b25369a2a51163ab1c93482cb8408726617/grpcio-1.78.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e888474dee2f59ff68130f8a397792d8cb8e17e6b3434339657ba4ee90845a8c", size = 6521579, upload-time = "2026-02-06T09:56:54.967Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/392e647d918004231e3d1c780ed125c48939bfc8f845adb8b5820410da3e/grpcio-1.78.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:86ce2371bfd7f212cf60d8517e5e854475c2c43ce14aa910e136ace72c6db6c1", size = 7199330, upload-time = "2026-02-06T09:56:57.611Z" }, + { url = "https://files.pythonhosted.org/packages/68/2f/42a52d78bdbdb3f1310ed690a3511cd004740281ca75d300b7bd6d9d3de3/grpcio-1.78.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0c689c02947d636bc7fab3e30cc3a3445cca99c834dfb77cd4a6cabfc1c5597", size = 6726696, upload-time = "2026-02-06T09:57:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/0f/83/b3d932a4fbb2dce3056f6df2926fc2d3ddc5d5acbafbec32c84033cf3f23/grpcio-1.78.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ce7599575eeb25c0f4dc1be59cada6219f3b56176f799627f44088b21381a28a", size = 7299076, upload-time = "2026-02-06T09:57:04.124Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d9/70ea1be55efaf91fd19f7258b1292772a8226cf1b0e237717fba671073cb/grpcio-1.78.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:684083fd383e9dc04c794adb838d4faea08b291ce81f64ecd08e4577c7398adf", size = 8284493, upload-time = "2026-02-06T09:57:06.746Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2f/3dddccf49e3e75564655b84175fca092d3efd81d2979fc89c4b1c1d879dc/grpcio-1.78.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab399ef5e3cd2a721b1038a0f3021001f19c5ab279f145e1146bb0b9f1b2b12c", size = 7724340, upload-time = "2026-02-06T09:57:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/79/ae/dfdb3183141db787a9363078a98764675996a7c2448883153091fd7c8527/grpcio-1.78.0-cp39-cp39-win32.whl", hash = "sha256:f3d6379493e18ad4d39537a82371c5281e153e963cecb13f953ebac155756525", size = 4077641, upload-time = "2026-02-06T09:57:11.881Z" }, + { url = "https://files.pythonhosted.org/packages/aa/aa/694b2f505345cfdd234cffb2525aa379a81695e6c02fd40d7e9193e871c6/grpcio-1.78.0-cp39-cp39-win_amd64.whl", hash = "sha256:5361a0630a7fdb58a6a97638ab70e1dae2893c4d08d7aba64ded28bb9e7a29df", size = 4799428, upload-time = "2026-02-06T09:57:14.493Z" }, ] [[package]] @@ -1674,7 +1691,7 @@ wheels = [ [[package]] name = "jax" -version = "0.9.0" +version = "0.9.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -1691,15 +1708,15 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "jaxlib", version = "0.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jaxlib", version = "0.9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "ml-dtypes", marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "opt-einsum", marker = "python_full_version >= '3.11'" }, { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/28/13186d20cbece4944577de774269c620b2f5f5ea163748b61e48e423f47f/jax-0.9.0.tar.gz", hash = "sha256:e5ce9d6991333aeaad37729dd8315d29c4b094ea9476a32fb49933b556c723fb", size = 2534495, upload-time = "2026-01-20T23:06:47.099Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/40/f85d1feadd8f793fc1bfab726272523ef34b27302b55861ea872ec774019/jax-0.9.0.1.tar.gz", hash = "sha256:e395253449d74354fa813ff9e245acb6e42287431d8a01ff33d92e9ee57d36bd", size = 2534795, upload-time = "2026-02-05T18:47:33.088Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/99/91fd58d495f03495e93b3f6100336d7a1df212aa90ad37d1322b8bb7df71/jax-0.9.0-py3-none-any.whl", hash = "sha256:be1c7c1fa91b338f071805a786f9366d4a08361b711b0568ef5a157226c68915", size = 2955442, upload-time = "2026-01-20T22:27:05.128Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/63ac22ec535e08129e16cb71b7eeeb8816c01d627ea1bc9105e925a71da0/jax-0.9.0.1-py3-none-any.whl", hash = "sha256:3baeaec6dc853394c272eb38a35ffba1972d67cf55d07a76bdb913bcd867e2ca", size = 2955477, upload-time = "2026-02-05T18:45:22.885Z" }, ] [[package]] @@ -1773,7 +1790,7 @@ wheels = [ [[package]] name = "jaxlib" -version = "0.9.0" +version = "0.9.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -1795,28 +1812,28 @@ dependencies = [ { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/96/71/283ee038a94869e7b3b3a27a594c028a58692e69f338bc30d0a466711830/jaxlib-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2770e2250e7923111f5e28813e593492fde3df40185b50bdc8bc41b866b99a8e", size = 56091849, upload-time = "2026-01-20T22:28:06.682Z" }, - { url = "https://files.pythonhosted.org/packages/1d/e9/100dd8a23407f5d883b684cd0ab9d23dfb5a889c17ba20ddc67fd6bb78d5/jaxlib-0.9.0-cp311-cp311-manylinux_2_27_aarch64.whl", hash = "sha256:1f21915b43b711f0abb3c35d26356bdcd16a68261d57fc8fefe370bc6dfcab4a", size = 74771277, upload-time = "2026-01-20T22:28:09.928Z" }, - { url = "https://files.pythonhosted.org/packages/a5/43/056cd98633ab0454c77b1ecebe1d5e21a76c06576a0ba655e9ac582ce985/jaxlib-0.9.0-cp311-cp311-manylinux_2_27_x86_64.whl", hash = "sha256:9f9eba014590435cec5fa1f6af2310a1a05db98d61331d18d6b278ff5424cd33", size = 80323264, upload-time = "2026-01-20T22:28:13.864Z" }, - { url = "https://files.pythonhosted.org/packages/85/75/abc1ad61eb08b8f47a3f752d9ab1ea89cdab93a48a279a3ea650ab0be47f/jaxlib-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:67d05eb00b9af04d5213a962c5f0551b0fb521efc27aab63e802233da1c7814b", size = 60482859, upload-time = "2026-01-20T22:28:16.869Z" }, - { url = "https://files.pythonhosted.org/packages/14/c1/ed8a199b71627f9c40b84a9fb40bba44bc048e62449633c5ee507066b19c/jaxlib-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c8d13bd74a55f9e5435587730b1ee067b17fc4c29b897f467c095003c63b37d", size = 56102192, upload-time = "2026-01-20T22:28:19.717Z" }, - { url = "https://files.pythonhosted.org/packages/5e/15/345f16517435b8f1deadd5bbfaae8dde42ed19af6e86ebbca0620279bab4/jaxlib-0.9.0-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:a19e0ad375161190073d12221b350b3da29f97d9e72b0bc6400d0dbf171cda5f", size = 74771957, upload-time = "2026-01-20T22:28:22.576Z" }, - { url = "https://files.pythonhosted.org/packages/20/7c/54ea33501e46b62b80071ed9cb96b0cfb28df88b7907edff0de41e9e9892/jaxlib-0.9.0-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:93258e8bbfad38f9207a649797e07bf6e7fb30a8dece67b1485f9d550c59121f", size = 80336468, upload-time = "2026-01-20T22:28:25.86Z" }, - { url = "https://files.pythonhosted.org/packages/c6/fe/1969b10690fb49f1fadcc616367857c54b16949a008778877e359e5da13b/jaxlib-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:c75f8d3ae8e8411fc90e6c18a99971ad4a5da8d200e5ad710d650e7c1d5652e6", size = 60508836, upload-time = "2026-01-20T22:28:29.275Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1a/a1db3023f66af906c85cdbafad88a0ae02df0d87ac7f749f1c9140ef6e3c/jaxlib-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:abcf50ca9f0f015f756035ff00803f5d04f42964775f936420d7afbf23b61c5c", size = 56102369, upload-time = "2026-01-20T22:28:32.554Z" }, - { url = "https://files.pythonhosted.org/packages/23/7e/ad2d0519939a45eb4a3151d5e41bef1d68eec7b3e7f961e09ef3bf2d6607/jaxlib-0.9.0-cp313-cp313-manylinux_2_27_aarch64.whl", hash = "sha256:7da962fcbdfc18093ea371cfb5420fc70b51d3160d15bef0b780e63126ad25c0", size = 74769548, upload-time = "2026-01-20T22:28:35.481Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d0/77bda8d946e3a854a5a533be7baf439bfffab9adae2a668a4dc645a494cd/jaxlib-0.9.0-cp313-cp313-manylinux_2_27_x86_64.whl", hash = "sha256:6aba184182c45e403b62dc269a04747da55da11d97586269710199204262d4ec", size = 80336519, upload-time = "2026-01-20T22:28:39.067Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/6e7891a226ee3fd717af26fdbc93ef6dcf587849a0020fdfc69ccb38ca7f/jaxlib-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:e81debd335315ef178fa995399653444e620c316abe86bc51477ac012e13efd4", size = 60508118, upload-time = "2026-01-20T22:28:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/32/42/75c4cbb31cd56d334839fe5bea132eb5c00f1298f9a04245c0951af5f623/jaxlib-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:de5d4920da2eb1182a0dbc01cfee9acf8617b64562a0b8f153fdfbcd00381f35", size = 56213023, upload-time = "2026-01-20T22:28:45.202Z" }, - { url = "https://files.pythonhosted.org/packages/06/af/91c23ae50bcfdc1dbeecd8ee4a7e87128a99a58bf6b6f6081215e8674a00/jaxlib-0.9.0-cp313-cp313t-manylinux_2_27_aarch64.whl", hash = "sha256:79caef921ebbbd78c239c3b4ed7644e0605bf454a066ad7700dde6c1e60b4b1e", size = 74887880, upload-time = "2026-01-20T22:28:48.227Z" }, - { url = "https://files.pythonhosted.org/packages/9a/64/7875e6ee446b558a366b3e9cbf26bab2390d87ac8015bc0dc49b35806b53/jaxlib-0.9.0-cp313-cp313t-manylinux_2_27_x86_64.whl", hash = "sha256:c857f062c720cff924f2237c925d46b8a8e8fde2b829ef2dc44cd4b6a7b8df88", size = 80440020, upload-time = "2026-01-20T22:28:51.378Z" }, - { url = "https://files.pythonhosted.org/packages/14/ce/5098b850ab4d667931ed4cd390aee0c866a5cb892270cddb692788168fd6/jaxlib-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:30460814e85e6cfda142726ffcb06d421ab60093ca248c7256c2ecfd10a81fd6", size = 56104537, upload-time = "2026-01-20T22:28:54.857Z" }, - { url = "https://files.pythonhosted.org/packages/b9/66/e6f7e2603a61c9ea0291744321a2e8866d275578cd68594ac40eda315aad/jaxlib-0.9.0-cp314-cp314-manylinux_2_27_aarch64.whl", hash = "sha256:b6b1ea090293c2e62df64c3fd4d46f85f60bb18b8253b3587a62ee53ad14690f", size = 74782881, upload-time = "2026-01-20T22:28:57.675Z" }, - { url = "https://files.pythonhosted.org/packages/50/49/b6773eba562747df115d8fbd0716625108e61af0001611a501ea9ad23eab/jaxlib-0.9.0-cp314-cp314-manylinux_2_27_x86_64.whl", hash = "sha256:881db842435ed40663c050745fdecf4f7b4850b1738a7538850d14e894798115", size = 80343444, upload-time = "2026-01-20T23:06:29.728Z" }, - { url = "https://files.pythonhosted.org/packages/5f/b0/ed338d3d5ff378a214a9e5fedce6c96b4705d921bffded709e01d209b998/jaxlib-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:d1077f30bd6608b12234f7d7a0cb0cf93f89c807ad69fee6c9146661f26e6b76", size = 62826259, upload-time = "2026-01-20T23:06:33.381Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c0/c172876423623282af055b4b46bcea1fd933afc3306455e5eb6e5ef18344/jaxlib-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:48e315c70509f7927e379c4dd98d6e2dc2a6198677a6e0c50f1cc44199ba50d3", size = 56210812, upload-time = "2026-01-20T23:06:36.154Z" }, - { url = "https://files.pythonhosted.org/packages/49/c1/8d051e9c2860ca228b98ba068daeadd7cdd0e8e06b4a93e27cab880e36ab/jaxlib-0.9.0-cp314-cp314t-manylinux_2_27_aarch64.whl", hash = "sha256:ef8006070aaceab7bec17eaa7e1d1b9d369793ad95927a14a27be216c063cd9c", size = 74885282, upload-time = "2026-01-20T23:06:40.22Z" }, - { url = "https://files.pythonhosted.org/packages/be/9a/b8daeec1fd3f492f9cbd5a8043032be6d24b23394254ff02ba0723f6b2e8/jaxlib-0.9.0-cp314-cp314t-manylinux_2_27_x86_64.whl", hash = "sha256:866ce0a67f59fb0cc70e9b7307e70cf61e8ea3c8bad841796a471162e20056f1", size = 80437354, upload-time = "2026-01-20T23:06:44.06Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fd/040321b0f4303ec7b558d69488c6130b1697c33d88dab0a0d2ccd2e0817c/jaxlib-0.9.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff2c550dab210278ed3a3b96454b19108a02e0795625be56dca5a181c9833c9", size = 56092920, upload-time = "2026-02-05T18:46:20.873Z" }, + { url = "https://files.pythonhosted.org/packages/e9/76/a558cd5e2ac8a2c16fe7f7e429dd5749cef48bc1a89941bb5b72bd3d7de3/jaxlib-0.9.0.1-cp311-cp311-manylinux_2_27_aarch64.whl", hash = "sha256:c4ac3cfd7aaacc37f37a6a332ee009dee39e3b5081bb4b473f410583436be553", size = 74767780, upload-time = "2026-02-05T18:46:23.917Z" }, + { url = "https://files.pythonhosted.org/packages/87/49/f72fb26e2feb100fd84d297a17111364b15d5979843f62b7539cd120f9bb/jaxlib-0.9.0.1-cp311-cp311-manylinux_2_27_x86_64.whl", hash = "sha256:dc95ee32ae2bd4ed947ad0218fd6576b50a60ce45b60714d7ff2fd9fa195ed9e", size = 80323754, upload-time = "2026-02-05T18:46:27.405Z" }, + { url = "https://files.pythonhosted.org/packages/55/fc/fa3c07d833a60cfb928f7a727fef25059e2e9af1dbc5d09821ad3a728292/jaxlib-0.9.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ed35e3300caa228c42897d8fbe961d6e03b797717e44eccbd3a788b5ac5c623", size = 60483840, upload-time = "2026-02-05T18:46:30.606Z" }, + { url = "https://files.pythonhosted.org/packages/c8/76/e89fd547f292663d8ce11b3247cd653a220e0d3cedbdbd094f0a8460d735/jaxlib-0.9.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3707bf0a58410da7c053c15ec6efee1fe12e70361416e055e4109b8041f4119b", size = 56104032, upload-time = "2026-02-05T18:46:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/c1/92/40d4f0acecb3d6f7078b9eb468e524778a3497d0882c7ecf80509c10b7d3/jaxlib-0.9.0.1-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:5ea8ebd62165b6f18f89b02fab749e02f5c584c2a1c703f04592d4d803f9e981", size = 74769175, upload-time = "2026-02-05T18:46:36.767Z" }, + { url = "https://files.pythonhosted.org/packages/1d/89/0dd938e6ed65ee994a49351a13aceaea46235ffbc1db5444d9ba3a279814/jaxlib-0.9.0.1-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:e0e4a0a24ef98ec021b913991fbda09aeb96481b1bc0e5300a0339aad216b226", size = 80339748, upload-time = "2026-02-05T18:46:40.148Z" }, + { url = "https://files.pythonhosted.org/packages/bb/02/265e5ccadd65fee2f0716431573d9e512e5c6aecb23f478a7a92053cf219/jaxlib-0.9.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:08733d1431238a7cf9108338ab7be898b97181cba0eef53f2f9fd3de17d20adb", size = 60508788, upload-time = "2026-02-05T18:46:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8d/f5a78b4d2a08e2d358e01527a3617af2df67c70231029ce1bdbb814219ff/jaxlib-0.9.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e857cafdd12e18493d96d4a290ed31aa9d99a0dc3056b4b42974c0f342c9bb0c", size = 56103168, upload-time = "2026-02-05T18:46:46.481Z" }, + { url = "https://files.pythonhosted.org/packages/47/c3/fd3a9e2f02c1a04a1a00ff74adb6dd09e34040587bbb1b51b0176151dfa1/jaxlib-0.9.0.1-cp313-cp313-manylinux_2_27_aarch64.whl", hash = "sha256:b73b85f927d9b006f07622d5676092eab916645c4804fed6568da5fb4a541dfc", size = 74768692, upload-time = "2026-02-05T18:46:49.571Z" }, + { url = "https://files.pythonhosted.org/packages/d9/48/34923a6add7dda5fb8f30409a98b638f0dbd2d9571dbbf73db958eaec44a/jaxlib-0.9.0.1-cp313-cp313-manylinux_2_27_x86_64.whl", hash = "sha256:54dd2d34c6bec4f099f888a2f7895069a47c3ba86aaa77b0b78e9c3f9ef948f1", size = 80337646, upload-time = "2026-02-05T18:46:53.299Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a9/629bed81406902653973d57de5af92842c7da63dfa8fcd84ee490c62ee94/jaxlib-0.9.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:27db7fbc49938f819f2a93fefef0bdc25bd523b499ab4d8a71ed8915c037c0b4", size = 60508306, upload-time = "2026-02-05T18:46:56.441Z" }, + { url = "https://files.pythonhosted.org/packages/45/e3/6943589aaa58d9934838e00c6149dd1fc81e0c8555e9fcc9f527648faf5c/jaxlib-0.9.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9312fcfb4c5586802c08bc1b3b2419e48aa2a4cd1356251fe791ad71edc2da2a", size = 56210697, upload-time = "2026-02-05T18:46:59.642Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ff/39479759b71f1d281b77050184759ac76dfd23a3ae75132ef92d168099c5/jaxlib-0.9.0.1-cp313-cp313t-manylinux_2_27_aarch64.whl", hash = "sha256:b536512cf84a0cb031196d6d5233f7093745e87eb416e45ad96fbb764b2befed", size = 74882879, upload-time = "2026-02-05T18:47:02.708Z" }, + { url = "https://files.pythonhosted.org/packages/87/0d/e41eeddd761110d733688d6493defe776440c8f3d114419a8ecaef55601f/jaxlib-0.9.0.1-cp313-cp313t-manylinux_2_27_x86_64.whl", hash = "sha256:c4dc8828bb236532033717061d132906075452556b12d1ff6ccc10e569435dfe", size = 80438424, upload-time = "2026-02-05T18:47:06.437Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ec/54b1251cea5c74a2f0d22106f5d1c7dc9e7b6a000d6a81a88deffa34c6fe/jaxlib-0.9.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:43272e52e5c89dbc4f02c7ccb6ffa5d587a09ac8db5163cb0c43e125b7075129", size = 56101484, upload-time = "2026-02-05T18:47:09.46Z" }, + { url = "https://files.pythonhosted.org/packages/29/ce/91ba780439aa1e6bae964ea641169e8b9c9349c175fcb1a723b96ba54313/jaxlib-0.9.0.1-cp314-cp314-manylinux_2_27_aarch64.whl", hash = "sha256:82348cee1521d6123038c4c3beeafa2076c8f4ae29a233b8abff9d6dc8b44145", size = 74789558, upload-time = "2026-02-05T18:47:12.394Z" }, + { url = "https://files.pythonhosted.org/packages/ce/9b/3d7baca233c378b01fa445c9f63b260f592249ff69950baf893cea631b10/jaxlib-0.9.0.1-cp314-cp314-manylinux_2_27_x86_64.whl", hash = "sha256:e61e88032eeb31339c72ead9ed60c6153cd2222512624caadea67c350c78432e", size = 80343053, upload-time = "2026-02-05T18:47:16.042Z" }, + { url = "https://files.pythonhosted.org/packages/92/5d/80efe5295133d5114fb7b0f27bdf82bc7a2308356dde6ba77c2afbaa3a36/jaxlib-0.9.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:abd9f127d23705105683448781914f17898b2b6591a051b259e6b947d4dcb93f", size = 62826248, upload-time = "2026-02-05T18:47:19.986Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a9/f72578daa6af9bed9bda75b842c97581b31a577d7b2072daf8ba3d5a8156/jaxlib-0.9.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b01a75fbac8098cc985f6f1690bfb62f98b0785c84199287e0baaae50fa4238", size = 56209722, upload-time = "2026-02-05T18:47:23.193Z" }, + { url = "https://files.pythonhosted.org/packages/95/ea/eefb118305dd5e1b0ad8d942f2bf43616c964d89fe491bec8628173da24d/jaxlib-0.9.0.1-cp314-cp314t-manylinux_2_27_aarch64.whl", hash = "sha256:76f23cbb109e673ea7a90781aca3e02a0c72464410c019fe14fba3c044f2b778", size = 74881382, upload-time = "2026-02-05T18:47:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/a42fb912fd1f9c83e22dc2577cdfbf1a1b07d6660532cb44724db7a7c479/jaxlib-0.9.0.1-cp314-cp314t-manylinux_2_27_x86_64.whl", hash = "sha256:f80d30dedce96c73a7f5dcb79c4c827a1bde2304f502a56ce7e7f723df2a5398", size = 80438052, upload-time = "2026-02-05T18:47:30.039Z" }, ] [[package]] @@ -2121,52 +2138,53 @@ wheels = [ [[package]] name = "line-profiler" -version = "5.0.0" +version = "5.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/5c/bbe9042ef5cf4c6cad4bf4d6f7975193430eba9191b7278ea114a3993fbb/line_profiler-5.0.0.tar.gz", hash = "sha256:a80f0afb05ba0d275d9dddc5ff97eab637471167ff3e66dcc7d135755059398c", size = 376919, upload-time = "2025-07-23T20:15:41.819Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/b8/8eb2bd10873a7bb93f412db8f735ff5b708bfcedef6492f2ec0a1f4dc55a/line_profiler-5.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5cd1621ff77e1f3f423dcc2611ef6fba462e791ce01fb41c95dce6d519c48ec8", size = 631005, upload-time = "2025-07-23T20:14:25.307Z" }, - { url = "https://files.pythonhosted.org/packages/36/af/a7fd6b2a83fbc10427e1fdd178a0a80621eeb3b29256b1856d459abb7d2a/line_profiler-5.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:17a44491d16309bc39fc6197b376a120ebc52adc3f50b0b6f9baf99af3124406", size = 490943, upload-time = "2025-07-23T20:14:27.396Z" }, - { url = "https://files.pythonhosted.org/packages/be/50/dcfc2e5386f5b3177cdad8eaa912482fe6a9218149a5cb5e85e871b55f2c/line_profiler-5.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a36a9a5ea5e37b0969a451f922b4dbb109350981187317f708694b3b5ceac3a5", size = 476487, upload-time = "2025-07-23T20:14:30.818Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/cdcfc640d5b3338891cd336b465c6112d9d5c2f56ced4f9ea3e795b192c6/line_profiler-5.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67e6e292efaf85d9678fe29295b46efd72c0d363b38e6b424df39b6553c49b3", size = 1412553, upload-time = "2025-07-23T20:14:32.07Z" }, - { url = "https://files.pythonhosted.org/packages/ca/23/bcdf3adf487917cfe431cb009b184c1a81a5099753747fe1a4aee42493f0/line_profiler-5.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9c92c28ee16bf3ba99966854407e4bc927473a925c1629489c8ebc01f8a640", size = 1461495, upload-time = "2025-07-23T20:14:33.396Z" }, - { url = "https://files.pythonhosted.org/packages/ec/04/1360ff19c4c426352ed820bba315670a6d52b3194fcb80af550a50e09310/line_profiler-5.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:51609cc264df6315cd9b9fa76d822a7b73a4f278dcab90ba907e32dc939ab1c2", size = 2510352, upload-time = "2025-07-23T20:14:34.797Z" }, - { url = "https://files.pythonhosted.org/packages/11/af/138966a6edfd95208e92e9cfef79595d6890df31b1749cc0244d36127473/line_profiler-5.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67f9721281655dc2b6763728a63928e3b8a35dfd6160c628a3c599afd0814a71", size = 2445103, upload-time = "2025-07-23T20:14:36.19Z" }, - { url = "https://files.pythonhosted.org/packages/92/95/5c9e4771f819b4d81510fa90b20a608bd3f91c268acd72747cd09f905de9/line_profiler-5.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:c2c27ac0c30d35ca1de5aeebe97e1d9c0d582e3d2c4146c572a648bec8efcfac", size = 461940, upload-time = "2025-07-23T20:14:37.422Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f7/4e0fd2610749136d60f3e168812b5f6c697ffcfbb167b10d4aac24af1223/line_profiler-5.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f32d536c056393b7ca703e459632edc327ff9e0fc320c7b0e0ed14b84d342b7f", size = 634587, upload-time = "2025-07-23T20:14:39.069Z" }, - { url = "https://files.pythonhosted.org/packages/d7/fe/b5458452c2dbf7a9590b5ad3cf4250710a2554a5a045bfa6395cdea1b2d5/line_profiler-5.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7da04ffc5a0a1f6653f43b13ad2e7ebf66f1d757174b7e660dfa0cbe74c4fc6", size = 492744, upload-time = "2025-07-23T20:14:40.632Z" }, - { url = "https://files.pythonhosted.org/packages/4c/e1/b69e20aeea8a11340f8c5d540c88ecf955a3559d8fbd5034cfe5677c69cf/line_profiler-5.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2746f6b13c19ca4847efd500402d53a5ebb2fe31644ce8af74fbeac5ea4c54c", size = 478101, upload-time = "2025-07-23T20:14:42.306Z" }, - { url = "https://files.pythonhosted.org/packages/0d/3b/b29e5539b2c98d2bd9f5651f10597dd70e07d5b09bb47cc0aa8d48927d72/line_profiler-5.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b4290319a59730c04cbd03755472d10524130065a20a695dc10dd66ffd92172", size = 1455927, upload-time = "2025-07-23T20:14:44.139Z" }, - { url = "https://files.pythonhosted.org/packages/82/1d/dcc75d2cf82bbe6ef65d0f39cc32410e099e7e1cd7f85b121a8d440ce8bc/line_profiler-5.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cd168a8af0032e8e3cb2fbb9ffc7694cdcecd47ec356ae863134df07becb3a2", size = 1508770, upload-time = "2025-07-23T20:14:45.868Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9f/cbf9d011381c878f848f824190ad833fbfeb5426eb6c42811b5b759d5d54/line_profiler-5.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cbe7b095865d00dda0f53d7d4556c2b1b5d13f723173a85edb206a78779ee07a", size = 2551269, upload-time = "2025-07-23T20:14:47.279Z" }, - { url = "https://files.pythonhosted.org/packages/7c/86/06999bff316e2522fc1d11fcd3720be81a7c47e94c785a9d93c290ae0415/line_profiler-5.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ff176045ea8a9e33900856db31b0b979357c337862ae4837140c98bd3161c3c7", size = 2491091, upload-time = "2025-07-23T20:14:48.637Z" }, - { url = "https://files.pythonhosted.org/packages/61/d1/758f2f569b5d4fdc667b88e88e7424081ba3a1d17fb531042ed7f0f08d7f/line_profiler-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:474e0962d02123f1190a804073b308a67ef5f9c3b8379184483d5016844a00df", size = 462954, upload-time = "2025-07-23T20:14:50.094Z" }, - { url = "https://files.pythonhosted.org/packages/73/d8/383c37c36f888c4ca82a28ffea27c589988463fc3f0edd6abae221c35275/line_profiler-5.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:729b18c0ac66b3368ade61203459219c202609f76b34190cbb2508b8e13998c8", size = 628109, upload-time = "2025-07-23T20:14:51.71Z" }, - { url = "https://files.pythonhosted.org/packages/54/a3/75a27b1f3e14ae63a2e99f3c7014dbc1e3a37f56c91b63a2fc171e72990d/line_profiler-5.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:438ed24278c428119473b61a473c8fe468ace7c97c94b005cb001137bc624547", size = 489142, upload-time = "2025-07-23T20:14:52.993Z" }, - { url = "https://files.pythonhosted.org/packages/8b/85/f65cdbfe8537da6fab97c42958109858df846563546b9c234a902a98c313/line_profiler-5.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:920b0076dca726caadbf29f0bfcce0cbcb4d9ff034cd9445a7308f9d556b4b3a", size = 475838, upload-time = "2025-07-23T20:14:54.637Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ea/cfa53c8ede0ef539cfe767a390d7ccfc015f89c39cc2a8c34e77753fd023/line_profiler-5.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53326eaad2d807487dcd45d2e385feaaed81aaf72b9ecd4f53c1a225d658006f", size = 1402290, upload-time = "2025-07-23T20:14:56.775Z" }, - { url = "https://files.pythonhosted.org/packages/e4/2c/3467cd5051afbc0eb277ee426e8dffdbd1fcdd82f1bc95a0cd8945b6c106/line_profiler-5.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e3995a989cdea022f0ede5db19a6ab527f818c59ffcebf4e5f7a8be4eb8e880", size = 1457827, upload-time = "2025-07-23T20:14:58.158Z" }, - { url = "https://files.pythonhosted.org/packages/d9/87/d5039608979b37ce3dadfa3eed7bf8bfec53b645acd30ca12c8088cf738d/line_profiler-5.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8bf57892a1d3a42273652506746ba9f620c505773ada804367c42e5b4146d6b6", size = 2497423, upload-time = "2025-07-23T20:15:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/59/3e/e5e09699e2841b4f41c16d01ff2adfd20fde6cb73cfa512262f0421e15e0/line_profiler-5.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43672085f149f5fbf3f08bba072ad7014dd485282e8665827b26941ea97d2d76", size = 2439733, upload-time = "2025-07-23T20:15:02.582Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cf/18d8fefabd8a56fb963f944149cadb69be67a479ce6723275cae2c943af5/line_profiler-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:446bd4f04e4bd9e979d68fdd916103df89a9d419e25bfb92b31af13c33808ee0", size = 460852, upload-time = "2025-07-23T20:15:03.827Z" }, - { url = "https://files.pythonhosted.org/packages/7d/eb/bc4420cf68661406c98d590656d72eed6f7d76e45accf568802dc83615ef/line_profiler-5.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9873fabbae1587778a551176758a70a5f6c89d8d070a1aca7a689677d41a1348", size = 624828, upload-time = "2025-07-23T20:15:05.315Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6e/6e0a4c1009975d27810027427d601acbad75b45947040d0fd80cec5b3e94/line_profiler-5.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2cd6cdb5a4d3b4ced607104dbed73ec820a69018decd1a90904854380536ed32", size = 487651, upload-time = "2025-07-23T20:15:06.961Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2c/e60e61f24faa0e6eca375bdac9c4b4b37c3267488d7cb1a8c5bd74cf5cdc/line_profiler-5.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:34d6172a3bd14167b3ea2e629d71b08683b17b3bc6eb6a4936d74e3669f875b6", size = 474071, upload-time = "2025-07-23T20:15:08.607Z" }, - { url = "https://files.pythonhosted.org/packages/e1/d5/6f178e74746f84cc17381f607d191c54772207770d585fda773b868bfe28/line_profiler-5.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5edd859be322aa8252253e940ac1c60cca4c385760d90a402072f8f35e4b967", size = 1405434, upload-time = "2025-07-23T20:15:09.862Z" }, - { url = "https://files.pythonhosted.org/packages/9b/32/ce67bbf81e5c78cc8d606afe6a192fbef30395021b2aaffe15681e186e3f/line_profiler-5.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4f97b223105eed6e525994f5653061bd981e04838ee5d14e01d17c26185094", size = 1467553, upload-time = "2025-07-23T20:15:11.195Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c1/431ffb89a351aaa63f8358442e0b9456a3bb745cebdf9c0d7aa4d47affca/line_profiler-5.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4758007e491bee3be40ebcca460596e0e28e7f39b735264694a9cafec729dfa9", size = 2442489, upload-time = "2025-07-23T20:15:12.602Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9d/e34cc99c8abca3a27911d3542a87361e9c292fa1258d182e4a0a5c442850/line_profiler-5.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:213b19c4b65942db5d477e603c18c76126e3811a39d8bab251d930d8ce82ffba", size = 461377, upload-time = "2025-07-23T20:15:13.871Z" }, - { url = "https://files.pythonhosted.org/packages/8a/4e/915be6af377c4824486e99abeefb94108c829f3b32f1ead72fc9c6e1e30e/line_profiler-5.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba2142d35a3401d348cb743611bac52ba9db9cf026f8aa82c34d13effb98a71", size = 632303, upload-time = "2025-07-23T20:15:26.796Z" }, - { url = "https://files.pythonhosted.org/packages/63/68/d5e9b22ae37d1e65188b754a0979d65fe339057b4d721c153f3fa1d89884/line_profiler-5.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17724b2dff0edb3a4ac402bef6381060a4c424fbaa170e651306495f7c95bba8", size = 491478, upload-time = "2025-07-23T20:15:28.367Z" }, - { url = "https://files.pythonhosted.org/packages/e7/37/7c4750068fb8977749071f08349ba7c330803f9312724948be9becb1d63d/line_profiler-5.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2315baca21a9be299b5a0a89f2ce4ed5cfd12ba039a82784a298dd106d3621d", size = 477089, upload-time = "2025-07-23T20:15:29.875Z" }, - { url = "https://files.pythonhosted.org/packages/68/b9/6c5beddc9eb5c6a3928c1f3849e75f5ce4fd9c7d81f83664ad20286ee61f/line_profiler-5.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:febbfc59502984e2cb0deb27cd163ed71847e36bbb82763f2bf3c9432cc440ab", size = 1409904, upload-time = "2025-07-23T20:15:32.236Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/94b284925890a671f5861ab82e3a98e3c4a73295144708fd6ade600d0ac9/line_profiler-5.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213dc34b1abdcafff944c13e62f2f1d254fc1cb30740ac0257e4567c8bea9a03", size = 1461377, upload-time = "2025-07-23T20:15:34.139Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a6/0733ba988122b8d077363cfbcb9ed143ceca0dbb3715c37285754c9d1daf/line_profiler-5.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:011ac8167855513cac266d698b34b8ded9c673640d105a715c989fd5f27a298c", size = 2507835, upload-time = "2025-07-23T20:15:36.28Z" }, - { url = "https://files.pythonhosted.org/packages/8b/59/93db811611fda1d921e56b324469ffb6b9210dd134bd377f52b3250012e2/line_profiler-5.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4646907f588439845d7739d6a5f10ab08a2f8952d65f61145eeb705e8bb4797e", size = 2444523, upload-time = "2025-07-23T20:15:38.531Z" }, - { url = "https://files.pythonhosted.org/packages/25/58/3d9355385817d64fc582daec8592eb85f0ea39d577001a2f1ce0971c4b95/line_profiler-5.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cb6dced51bf906ddf2a8d75eda3523cee4cfb0102f54610e8f849630341a281", size = 461954, upload-time = "2025-07-23T20:15:40.281Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/bf/2a/498665a424404a560b2c6b3a3ea8b4304dbe493ccc3d01a6866c7a38890e/line_profiler-5.0.1.tar.gz", hash = "sha256:3e56c5eee51aa8b82a09d8a35ab19f4100ee45d70eb676c2c58deedcc06c34b1", size = 406557, upload-time = "2026-02-07T05:06:49.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/68/0a52f3868aca7722938094003bda3f05a36a5ac72a3faa3b468fb939ffc4/line_profiler-5.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:64f099711f4752bc4e3dae762b971ef3016ad7572507db4b22a9a7bb0f4fd05f", size = 657258, upload-time = "2026-02-07T05:05:15.094Z" }, + { url = "https://files.pythonhosted.org/packages/4a/00/8609a3774a221aa4c48c3d5f3ecf63194e44c931b74a3bad6637057f07c4/line_profiler-5.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c55ae9dd54deda74ddb79a60818a59c32d53e87eb5628eab53183123aca6c53", size = 514405, upload-time = "2026-02-07T05:05:17.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1b/208adab75d25140c6ba4469da3e4d8bf51bb65a0e9e5b04f24dd0e6dadc7/line_profiler-5.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e6502518f7d7c1241b8d72fce275a8d8ac08081335a1bd99ad69fadaf044784d", size = 502984, upload-time = "2026-02-07T05:05:18.866Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d9/fbc770fa6df84ea32580dae6c46447c07831fac97f3e5e5f3f6182c7d5ab/line_profiler-5.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b9dd773b1bde3f9864bb302e8bb78a9b35573448e1b837e8a6d2740580ff18e", size = 1505200, upload-time = "2026-02-07T05:05:20.428Z" }, + { url = "https://files.pythonhosted.org/packages/1f/12/77c03fcb93b0d206b785ed45f461b29195bdd9cfd609ced3cdfb654287b3/line_profiler-5.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b28c8902092e1dbc1aa35929e7b5472a5bdb32da1fbd3570c5e78376a71ee86", size = 2530747, upload-time = "2026-02-07T05:05:21.501Z" }, + { url = "https://files.pythonhosted.org/packages/96/3d/a001ec8c4154cbfd949bd570036163e8a7dbeca84a8a82c03cf33919bdcd/line_profiler-5.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:3b8bf20a9a15029e833361d6c8bed4397c806725a80a2bfb457ce1d60a918dfe", size = 485432, upload-time = "2026-02-07T05:05:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/f9/55/0a74021f3ecfe71be86b3263f98890a28902ed0715a841507ac2eb0316db/line_profiler-5.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8103e12337802850af69ad74fa2d540cb24b35905cab5d093e4d5a88f89d7305", size = 656106, upload-time = "2026-02-07T05:05:56.919Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/63b961fe4ce9cd9b05a4710858b32c537ad8364ed84ec52b1a463733b8b9/line_profiler-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:029abeda3bedf2205fd9e9f9d35b38d369cc33d5581d875aa27c80b03facd95e", size = 513554, upload-time = "2026-02-07T05:05:59.044Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0c15fe6ae98340a8315f76a289720b3db7cfd2b43581f07771b39ac59a69/line_profiler-5.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fc5e42b471316fe55fb52f3dd048a359652d3715e302707a4342844ade009166", size = 502698, upload-time = "2026-02-07T05:06:00.11Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9c/2b0ede405364e23a5ec45100a6c053db40afff36b17d2778541e16766cae/line_profiler-5.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5fe36bf67e5114b56956017cbdb3e14851afa047aee06a6249c7e4524985d30", size = 1546113, upload-time = "2026-02-07T05:06:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c8/80bd62dd8fd4d594cb9bc12f40ade5222c5e18a7073f2003091d53ee264a/line_profiler-5.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:daab2aa2a1c67e7706ab43e13b544eb7c0e2321d7a0646e0380745361e2477ce", size = 2570825, upload-time = "2026-02-07T05:06:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/20/75/87a0b452a42783848a82ca67a390f920a5844ef0db092f9029cc42933a72/line_profiler-5.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a717a5eed30311982b8e707eda30384c5532ccbd557d57e40a1dbc5588667c3", size = 486002, upload-time = "2026-02-07T05:06:04.085Z" }, + { url = "https://files.pythonhosted.org/packages/54/79/0bf2de84d3680318bf85f3375fe0c296c6d4b1ed02dcad686fa09ced8df1/line_profiler-5.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:6d4b626c948be1d7742ea2314261eccfc4b9f7dfb2adae8ece4409776a9e2511", size = 470516, upload-time = "2026-02-07T05:06:05.227Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8e/bd5b0cc87203ff280cf01ef65b263472983adad5a0f710cf191e292fc3df/line_profiler-5.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b9b58a4d805ea11a0ea18c75a21b9d3bc1bb69d1f7d9282625386b8b47689b3b", size = 652481, upload-time = "2026-02-07T05:06:06.329Z" }, + { url = "https://files.pythonhosted.org/packages/a0/26/01d65c99809cdec0566c3f86b4cefec6ba558b261f75dac0b856a1570d7e/line_profiler-5.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a5401dfe1dcd6f01d0f35feff02c96ebd73d2e45058e39ba935e822bde33f191", size = 511256, upload-time = "2026-02-07T05:06:07.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/4d/5862629dc59f8154eae76ac0ea2a69c0d11b0b79483957f3c1c6a1af9896/line_profiler-5.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3350cfe27fa71082ac3d773d905b5cff7584a7923a36ea894a4619c0eb40116", size = 501428, upload-time = "2026-02-07T05:06:08.889Z" }, + { url = "https://files.pythonhosted.org/packages/81/1d/adda8aff5cc3e1d8687a128593a562fbf28d650513674aa773381068ce95/line_profiler-5.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:348f34f54d68dcb249124d6b6275cbfcaea33920aecdb2f7d536d395abbaeda7", size = 1489869, upload-time = "2026-02-07T05:06:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/96/6e/a5f92fb2451982ea49dd1bbc1b4a308aaeda81320583b3593731bc8654e8/line_profiler-5.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4344e66d853824be0f0fa5d99ba6014cb093334e178fac942870bc4a4dd4c146", size = 2501328, upload-time = "2026-02-07T05:06:12.101Z" }, + { url = "https://files.pythonhosted.org/packages/5f/79/cd66262b78a9f1e6ccd7452f331237c3489fb93191f95fe0b9c4cdac4733/line_profiler-5.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:87d2295feaa5ac933e672d1c5ac5b83e2a1f7ebce25d290f81a7aabb1d46ac1f", size = 484346, upload-time = "2026-02-07T05:06:13.847Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ba/ec80db0e0b2a46832127f5de5cd6d059d60aeb0daf2a2eddd7a05ff092da/line_profiler-5.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:d3c93c975c1ccbc77db82579e9ec65897d783d53c3456cd2a8a582cae7cb5b81", size = 467828, upload-time = "2026-02-07T05:06:14.896Z" }, + { url = "https://files.pythonhosted.org/packages/35/83/23b24ceb224f89725c2baa0be1b889ea9eec84b4ec3835c8f7ff62abf918/line_profiler-5.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6100734916900f6c0ee5ba1cae05e7860c53aac4cd7a016faefd50092be22a14", size = 648194, upload-time = "2026-02-07T05:06:16.59Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ec/6e71a59baf77b95c38ac07dc6e622f46674a526ea9dbd348ac310c24b358/line_profiler-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef083f93bbb8cd8e7fa49b07e09245195a9be47755e7e353fb526aee9d983427", size = 509348, upload-time = "2026-02-07T05:06:18.195Z" }, + { url = "https://files.pythonhosted.org/packages/46/29/ce75d7e9c07e72ffa513424881d0509a559a21a433f462fb197604a0e4ce/line_profiler-5.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b74a89eba20a20222bf6220e017244849cb675125a0e9e7ade5411af3d6c246", size = 499198, upload-time = "2026-02-07T05:06:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/90/ae/3bccce627f42151b2bd7389ef1304b9255e38d6c79ae23fbd8c33600ea45/line_profiler-5.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f8a74fb63ff4cb1358fa9378daa853396f5868d5c81cad88d17b1f48a761f04", size = 1488964, upload-time = "2026-02-07T05:06:20.86Z" }, + { url = "https://files.pythonhosted.org/packages/ff/24/0940490a9be8e19ed097da03463547c5a7e066b8612e208e005fd440c3e2/line_profiler-5.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7460781af7e8754850c5dc8b6f1d0133d48aa3a24723cfe9d445dd27d42a798d", size = 2500824, upload-time = "2026-02-07T05:06:22.19Z" }, + { url = "https://files.pythonhosted.org/packages/3e/53/f73fc9515d3919c9733b88fc9d51b81dba50d74da9e8f357a72ed5c503b7/line_profiler-5.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:20174e74b142d13ccb8836ebabfa1ca4e2cde4d0961f3ee078a3cc64f2832bd6", size = 484852, upload-time = "2026-02-07T05:06:23.467Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/2cdf45c274410632c15a28075ccc865e13b2dd5ae3b11a25313cf8e0d8af/line_profiler-5.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:39ed06465de1dc1eccf1df7dcd90aa100af3f55472ef25fa6c8bd228d8d5f819", size = 467511, upload-time = "2026-02-07T05:06:24.588Z" }, + { url = "https://files.pythonhosted.org/packages/dd/76/f857c647597bca495dcba3f7edaf986516bde919152f19c71bef47a546fa/line_profiler-5.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8c0cd9f38eaddb506990080593381f276b1942c416e415a933632c4943895df3", size = 653895, upload-time = "2026-02-07T05:06:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3c/7688dff38a2bdcf66b990f5d7c496ca41dc63171a3e03a6049488842f786/line_profiler-5.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4e901d75109f12a1a65edc2352401875cd51b69bf91537a9555c7691fdc0dd46", size = 514383, upload-time = "2026-02-07T05:06:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/93/4a/79513220bc2c4fa2a4e7468b89e18b917e82bc7ea1e7be1b924412f9cd20/line_profiler-5.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5630d495f16babd812f4ef5cba90cf3cf3cc06b10a24f9becfb76a64e511bcbd", size = 505430, upload-time = "2026-02-07T05:06:28.216Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f4/012446292f1fee6c4a5b7ebf3d5de7741550b8b3e781186a32c333ced1fa/line_profiler-5.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d9c0b8d01eddb99ed76f53e2f81cce8ceff68e751370af2bd1fd276fb17570e", size = 1480761, upload-time = "2026-02-07T05:06:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e1/48aefe03d27a32b93ffec6aaaab1e0f5d5b94e0a44b3ddf0929c9eeef50c/line_profiler-5.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b76d6f7ab6d2b3018bea10172bbe105624d14f63bde9549c393502ca4ea9fb5", size = 2500278, upload-time = "2026-02-07T05:06:30.782Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f8/0959ab4ff46a99c9db6d90de90d08bff6d3277fc4b80c9fb5d04300af798/line_profiler-5.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:9bb97a77d8d5ffa8bf0193c5ee4d804dc8360244861f104c10c9e58c95721066", size = 491243, upload-time = "2026-02-07T05:06:32.087Z" }, + { url = "https://files.pythonhosted.org/packages/54/6d/91e7e2390c064233c1e64de8d82059212814c29b46f33f554bc7fe0a2711/line_profiler-5.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:c3a4807a30adda81ac246744bf42bff8cc54bcbbe5e3bfff4523b171349c5059", size = 475314, upload-time = "2026-02-07T05:06:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/0a0c4a2bb84de941e52a46642341552c721d091e0a4d7be5138849de4902/line_profiler-5.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:92b724b3668755967a2174c20b56e7a69ce46aea1935f1605bc7f5f5ed672f15", size = 658806, upload-time = "2026-02-07T05:06:42.141Z" }, + { url = "https://files.pythonhosted.org/packages/ad/79/b7a36d46cff3f4d17d18e8c3d6e8275ac05559952e25dc4c95e8c4cf7337/line_profiler-5.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75eba02f688601a9ef23d709787c2e2e26f8c46de9b883d60227ef391dd8c513", size = 515234, upload-time = "2026-02-07T05:06:43.211Z" }, + { url = "https://files.pythonhosted.org/packages/88/af/a8aaf394f1a15df4cbcfabc228c215dc014082a864f38d4b074fc63caef8/line_profiler-5.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b3b551c90eff946c38933c76c4c77e2904c403a20dc9eb467b756042066e6a4", size = 503766, upload-time = "2026-02-07T05:06:44.303Z" }, + { url = "https://files.pythonhosted.org/packages/6d/37/c0c27f093a2352fa5d491a0404beb8b8ea1a56a8e88d61081160ef284da3/line_profiler-5.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c53434bd2885938a46eee75373d5a5fef724803578a2262076ce4693032c6d", size = 1501758, upload-time = "2026-02-07T05:06:45.403Z" }, + { url = "https://files.pythonhosted.org/packages/d7/55/c2160db00c0c07a044f6f29034bb441c5c3eb29e907590a823cdfede8ad3/line_profiler-5.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ac96cc3c3946a9bfbb723843a0eceeed3295d633fe65960e3ed096d31b065eab", size = 2524435, upload-time = "2026-02-07T05:06:46.716Z" }, + { url = "https://files.pythonhosted.org/packages/e6/92/262533d5bb1fa81da52d1a6d2dc828c05a578fe4ed4506fb6feaa00f14d6/line_profiler-5.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7f17946e5cf2cdcf8406656bebc0ba8fb9550b4a0558bce52e2b8e2c047d1a3", size = 486001, upload-time = "2026-02-07T05:06:48.63Z" }, ] [[package]] @@ -2424,7 +2442,7 @@ wheels = [ [[package]] name = "markdown" -version = "3.10.1" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -2441,9 +2459,9 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -3570,11 +3588,11 @@ wheels = [ [[package]] name = "parso" -version = "0.8.5" +version = "0.8.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, ] [[package]] @@ -3591,7 +3609,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "ptyprocess" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -3897,7 +3915,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.8.0" +version = "7.8.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -3922,33 +3940,33 @@ dependencies = [ { name = "six", marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/39/613f56a5d469e4c4f4e9616f533bd0451ae1b7b70d033201227b9229bf17/posthog-7.8.0.tar.gz", hash = "sha256:5f46730090be503a9d4357905d3260178ed6be4c1f6c666e8d7b44189e11fbb8", size = 167014, upload-time = "2026-01-30T13:43:29.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/ad/2f116cd9b83dc83ece4328a4efe0bcb80e5c2993837f89a788467d261da8/posthog-7.8.3.tar.gz", hash = "sha256:2b85e818bf818ac2768a890b772b7c12d4f909797226acd9327d66a319dbcf83", size = 167083, upload-time = "2026-02-06T13:16:22.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/f6/c3118de9b52fd442c0de92e4ad68326f5ecb327c1d354e0b9a8d0213ce45/posthog-7.8.0-py3-none-any.whl", hash = "sha256:fefa48c560c51ca0acc6261c92a8f61a067a8aa977c8820d0f149eaa4500e4da", size = 192427, upload-time = "2026-01-30T13:43:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e5/5a4b060cbb9aa9defb8bfd55d15899b3146fece14147f4d66be80e81955a/posthog-7.8.3-py3-none-any.whl", hash = "sha256:1840796e4f7e14dd91ec5fdeb939712c3383fe9e758cfcdeb0317d8f30f7b901", size = 192528, upload-time = "2026-02-06T13:16:21.385Z" }, ] [[package]] name = "prek" -version = "0.3.1" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/62/4b91c8a343e21fcefabc569a91d08cf8756c554332521af78294acef7c27/prek-0.3.1.tar.gz", hash = "sha256:45abc4ffd3cb2d39c478f47e92e88f050e5a4b7a20915d78e54b1a3d3619ebe4", size = 323141, upload-time = "2026-01-31T13:25:58.128Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/f5/ee52def928dd1355c20bcfcf765e1e61434635c33f3075e848e7b83a157b/prek-0.3.2.tar.gz", hash = "sha256:dce0074ff1a21290748ca567b4bda7553ee305a8c7b14d737e6c58364a499364", size = 334229, upload-time = "2026-02-06T13:49:47.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/c1/e0e545048e4190245fb4ae375d67684108518f3c69b67b81d62e1cd855c6/prek-0.3.1-py3-none-linux_armv6l.whl", hash = "sha256:1f77d0845cc63cad9c447f7f7d554c1ad188d07dbe02741823d20d58c7312eaf", size = 4285460, upload-time = "2026-01-31T13:25:42.066Z" }, - { url = "https://files.pythonhosted.org/packages/10/fe/7636d10e2dafdf2a4a927c989f32ce3f08e99d62ebad7563f0272e74b7f4/prek-0.3.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e21142300d139e8c7f3970dd8aa66391cb43cd17c0c4ee65ff1af48856bb6a4b", size = 4287085, upload-time = "2026-01-31T13:25:40.193Z" }, - { url = "https://files.pythonhosted.org/packages/a3/7f/62ed57340071e04c02057d64276ec3986baca3ad4759523e2f433dc9be55/prek-0.3.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c09391de7d1994f9402c46cb31671800ea309b1668d839614965706206da3655", size = 3936188, upload-time = "2026-01-31T13:25:47.314Z" }, - { url = "https://files.pythonhosted.org/packages/6b/17/cb24f462c255f76d130ca110f4fcec09b041c3c770e43960cc3fc9dcc9ce/prek-0.3.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a0b0a012ef6ef28dee019cf81a95c5759b2c03287c32c1f2adcb5f5fb097138e", size = 4275214, upload-time = "2026-01-31T13:25:38.053Z" }, - { url = "https://files.pythonhosted.org/packages/f2/85/db155b59d73cf972c8467e4d95def635f9976d5fcbcc790a4bbe9d2e049a/prek-0.3.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4a0e40785d39b8feae0d7ecf5534789811a2614114ab47f4e344a2ebd75ac10", size = 4197982, upload-time = "2026-01-31T13:25:50.034Z" }, - { url = "https://files.pythonhosted.org/packages/06/cf/d35c32436692928a9ca53eed3334e30148a60f0faa33c42e8d11b6028fa6/prek-0.3.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5c2f5e377e3cc5a5ea191deb8255a5823fbaa01b424417fe18ff12c7c800f3", size = 4458572, upload-time = "2026-01-31T13:25:51.46Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c0/eb36fecb21fe30baa72fb87ccf3a791c32932786c287f95f8972402e9245/prek-0.3.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fe70e97f4dfca57ce142caecd77b90a435abd1c855f9e96041078415d80e89a", size = 4999230, upload-time = "2026-01-31T13:25:44.055Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f3/ad1a25ea16320e6acd1fedd6bd96a0d22526f5132d9b5adc045996ccca4c/prek-0.3.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b28e921d893771bdd7533cd94d46a10e0cf2855c5e6bf6809b598b5e45baa73", size = 4510376, upload-time = "2026-01-31T13:25:48.563Z" }, - { url = "https://files.pythonhosted.org/packages/39/b7/91afdd24be808ccf3b9119f4cf2bd6d02e30683143a62a08f432a3435861/prek-0.3.1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:555610a53c4541976f4fe8602177ecd7a86a931dcb90a89e5826cfc4a6c8f2cb", size = 4273229, upload-time = "2026-01-31T13:25:56.362Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bb/636c77c5c9fc5eadcc2af975a95b48eeeff2dc833021e222b0e9479b9b47/prek-0.3.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:663a15a31b705db5d01a1c9eb28c6ea417628e6cb68de2dc7b3016ca2f959a99", size = 4301166, upload-time = "2026-01-31T13:25:36.281Z" }, - { url = "https://files.pythonhosted.org/packages/4e/cf/c928a36173e71b21b252943404a5b3d1ddc1f08c9e0f1d7282a2c62c7325/prek-0.3.1-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:1d03fb5fa37177dc37ccbe474252473adcbde63287a63e9fa3d5745470f95bd8", size = 4188473, upload-time = "2026-01-31T13:25:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/98/4c/af8f6a40cb094e88225514b89e8ae05ac69fc479d6b500e4b984f9ef8ae3/prek-0.3.1-py3-none-musllinux_1_1_i686.whl", hash = "sha256:3e20a5b704c06944dca9555a39f1de3622edc8ed2becaf8b3564925d1b7c1c0d", size = 4342239, upload-time = "2026-01-31T13:25:55.179Z" }, - { url = "https://files.pythonhosted.org/packages/b7/ba/6b0f725c0cf96182ab9622b4d122a01f04de9b2b6e4a6516874390218510/prek-0.3.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6889acf0c9b0dd7b9cd3510ec36af10a739826d2b9de1e2d021ae6a9f130959a", size = 4618674, upload-time = "2026-01-31T13:25:59.175Z" }, - { url = "https://files.pythonhosted.org/packages/d8/49/caa320893640c9e72ed3d3e2bab7959faba54cefeea09be18c33d5f75baf/prek-0.3.1-py3-none-win32.whl", hash = "sha256:cc62a4bff79ed835d448098f0667c1a91915cec9cfac6c6246b675f0da46eded", size = 3928699, upload-time = "2026-01-31T13:25:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/59/19/15907abe245ae9a120abcebb0c50c6d456ba719eb640f7c57e4f5b2f00e3/prek-0.3.1-py3-none-win_amd64.whl", hash = "sha256:3515140def20bab85e53585b0beb90d894ff2915d2fdb4451b6a4588e16516d8", size = 4296106, upload-time = "2026-01-31T13:25:45.606Z" }, - { url = "https://files.pythonhosted.org/packages/a6/5e/9b994b5de36d6aa5caaf09a018d8fe4820db46e4da577c2fd7a1e176b56c/prek-0.3.1-py3-none-win_arm64.whl", hash = "sha256:cfa58365eb36753cff684dc3b00196c1163bb135fe72c6a1c6ebb1a179f5dbdf", size = 4021714, upload-time = "2026-01-31T13:25:34.993Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/70a5fc881290a63910494df2677c0fb241d27cfaa435bbcd0de5cd2e2443/prek-0.3.2-py3-none-linux_armv6l.whl", hash = "sha256:4f352f9c3fc98aeed4c8b2ec4dbf16fc386e45eea163c44d67e5571489bd8e6f", size = 4614960, upload-time = "2026-02-06T13:50:05.818Z" }, + { url = "https://files.pythonhosted.org/packages/c0/15/a82d5d32a2207ccae5d86ea9e44f2b93531ed000faf83a253e8d1108e026/prek-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4a000cfbc3a6ec7d424f8be3c3e69ccd595448197f92daac8652382d0acc2593", size = 4622889, upload-time = "2026-02-06T13:49:53.662Z" }, + { url = "https://files.pythonhosted.org/packages/89/75/ea833b58a12741397017baef9b66a6e443bfa8286ecbd645d14111446280/prek-0.3.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5436bdc2702cbd7bcf9e355564ae66f8131211e65fefae54665a94a07c3d450a", size = 4239653, upload-time = "2026-02-06T13:50:02.88Z" }, + { url = "https://files.pythonhosted.org/packages/10/b4/d9c3885987afac6e20df4cb7db14e3b0d5a08a77ae4916488254ebac4d0b/prek-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0161b5f584f9e7f416d6cf40a17b98f17953050ff8d8350ec60f20fe966b86b6", size = 4595101, upload-time = "2026-02-06T13:49:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/1a06473ed83dbc898de22838abdb13954e2583ce229f857f61828384634c/prek-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e641e8533bca38797eebb49aa89ed0e8db0e61225943b27008c257e3af4d631", size = 4521978, upload-time = "2026-02-06T13:49:41.266Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5e/c38390d5612e6d86b32151c1d2fdab74a57913473193591f0eb00c894c21/prek-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfca1810d49d3f9ef37599c958c4e716bc19a1d78a7e88cbdcb332e0b008994f", size = 4829108, upload-time = "2026-02-06T13:49:44.598Z" }, + { url = "https://files.pythonhosted.org/packages/80/a6/cecce2ab623747ff65ed990bb0d95fa38449ee19b348234862acf9392fff/prek-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d69d754299a95a85dc20196f633232f306bee7e7c8cba61791f49ce70404ec", size = 5357520, upload-time = "2026-02-06T13:49:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/a5/18/d6bcb29501514023c76d55d5cd03bdbc037737c8de8b6bc41cdebfb1682c/prek-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:539dcb90ad9b20837968539855df6a29493b328a1ae87641560768eed4f313b0", size = 4852635, upload-time = "2026-02-06T13:49:58.347Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0a/ae46f34ba27ba87aea5c9ad4ac9cd3e07e014fd5079ae079c84198f62118/prek-0.3.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:1998db3d0cbe243984736c82232be51318f9192e2433919a6b1c5790f600b5fd", size = 4599484, upload-time = "2026-02-06T13:49:43.296Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a9/73bfb5b3f7c3583f9b0d431924873928705cdef6abb3d0461c37254a681b/prek-0.3.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:07ab237a5415a3e8c0db54de9d63899bcd947624bdd8820d26f12e65f8d19eb7", size = 4657694, upload-time = "2026-02-06T13:50:01.074Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/0994bc176e1a80110fad3babce2c98b0ac4007630774c9e18fc200a34781/prek-0.3.2-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0ced19701d69c14a08125f14a5dd03945982edf59e793c73a95caf4697a7ac30", size = 4509337, upload-time = "2026-02-06T13:49:54.891Z" }, + { url = "https://files.pythonhosted.org/packages/f9/13/e73f85f65ba8f626468e5d1694ab3763111513da08e0074517f40238c061/prek-0.3.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ffb28189f976fa111e770ee94e4f298add307714568fb7d610c8a7095cb1ce59", size = 4697350, upload-time = "2026-02-06T13:50:04.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/47/98c46dcd580305b9960252a4eb966f1a7b1035c55c363f378d85662ba400/prek-0.3.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f63134b3eea14421789a7335d86f99aee277cb520427196f2923b9260c60e5c5", size = 4955860, upload-time = "2026-02-06T13:49:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/73/42/1bb4bba3ff47897df11e9dfd774027cdfa135482c961a54e079af0faf45a/prek-0.3.2-py3-none-win32.whl", hash = "sha256:58c806bd1344becd480ef5a5ba348846cc000af0e1fbe854fef91181a2e06461", size = 4267619, upload-time = "2026-02-06T13:49:39.503Z" }, + { url = "https://files.pythonhosted.org/packages/97/11/6665f47a7c350d83de17403c90bbf7a762ef50876ece456a86f64f46fbfb/prek-0.3.2-py3-none-win_amd64.whl", hash = "sha256:70114b48e9eb8048b2c11b4c7715ce618529c6af71acc84dd8877871a2ef71a6", size = 4624324, upload-time = "2026-02-06T13:49:45.922Z" }, + { url = "https://files.pythonhosted.org/packages/22/e7/740997ca82574d03426f897fd88afe3fc8a7306b8c7ea342a8bc1c538488/prek-0.3.2-py3-none-win_arm64.whl", hash = "sha256:9144d176d0daa2469a25c303ef6f6fa95a8df015eb275232f5cb53551ecefef0", size = 4336008, upload-time = "2026-02-06T13:49:52.27Z" }, ] [[package]] @@ -3998,6 +4016,132 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] +[[package]] +name = "pyarrow" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9.2' and python_full_version < '3.10'", + "python_full_version < '3.9.2'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/d9/110de31880016e2afc52d8580b397dbe47615defbf09ca8cf55f56c62165/pyarrow-21.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e563271e2c5ff4d4a4cbeb2c83d5cf0d4938b891518e676025f7268c6fe5fe26", size = 31196837, upload-time = "2025-07-18T00:54:34.755Z" }, + { url = "https://files.pythonhosted.org/packages/df/5f/c1c1997613abf24fceb087e79432d24c19bc6f7259cab57c2c8e5e545fab/pyarrow-21.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fee33b0ca46f4c85443d6c450357101e47d53e6c3f008d658c27a2d020d44c79", size = 32659470, upload-time = "2025-07-18T00:54:38.329Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ed/b1589a777816ee33ba123ba1e4f8f02243a844fed0deec97bde9fb21a5cf/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7be45519b830f7c24b21d630a31d48bcebfd5d4d7f9d3bdb49da9cdf6d764edb", size = 41055619, upload-time = "2025-07-18T00:54:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/44/28/b6672962639e85dc0ac36f71ab3a8f5f38e01b51343d7aa372a6b56fa3f3/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:26bfd95f6bff443ceae63c65dc7e048670b7e98bc892210acba7e4995d3d4b51", size = 42733488, upload-time = "2025-07-18T00:54:47.132Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/de02c3614874b9089c94eac093f90ca5dfa6d5afe45de3ba847fd950fdf1/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd04ec08f7f8bd113c55868bd3fc442a9db67c27af098c5f814a3091e71cc61a", size = 43329159, upload-time = "2025-07-18T00:54:51.686Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3e/99473332ac40278f196e105ce30b79ab8affab12f6194802f2593d6b0be2/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9b0b14b49ac10654332a805aedfc0147fb3469cbf8ea951b3d040dab12372594", size = 45050567, upload-time = "2025-07-18T00:54:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f5/c372ef60593d713e8bfbb7e0c743501605f0ad00719146dc075faf11172b/pyarrow-21.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d9f8bcb4c3be7738add259738abdeddc363de1b80e3310e04067aa1ca596634", size = 26217959, upload-time = "2025-07-18T00:55:00.482Z" }, + { url = "https://files.pythonhosted.org/packages/94/dc/80564a3071a57c20b7c32575e4a0120e8a330ef487c319b122942d665960/pyarrow-21.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b", size = 31243234, upload-time = "2025-07-18T00:55:03.812Z" }, + { url = "https://files.pythonhosted.org/packages/ea/cc/3b51cb2db26fe535d14f74cab4c79b191ed9a8cd4cbba45e2379b5ca2746/pyarrow-21.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10", size = 32714370, upload-time = "2025-07-18T00:55:07.495Z" }, + { url = "https://files.pythonhosted.org/packages/24/11/a4431f36d5ad7d83b87146f515c063e4d07ef0b7240876ddb885e6b44f2e/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e", size = 41135424, upload-time = "2025-07-18T00:55:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/74/dc/035d54638fc5d2971cbf1e987ccd45f1091c83bcf747281cf6cc25e72c88/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569", size = 42823810, upload-time = "2025-07-18T00:55:16.301Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/89fced102448a9e3e0d4dded1f37fa3ce4700f02cdb8665457fcc8015f5b/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e", size = 43391538, upload-time = "2025-07-18T00:55:23.82Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/ea7f1bd08978d39debd3b23611c293f64a642557e8141c80635d501e6d53/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c", size = 45120056, upload-time = "2025-07-18T00:55:28.231Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0b/77ea0600009842b30ceebc3337639a7380cd946061b620ac1a2f3cb541e2/pyarrow-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6", size = 26220568, upload-time = "2025-07-18T00:55:32.122Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload-time = "2025-07-18T00:55:35.373Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload-time = "2025-07-18T00:55:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload-time = "2025-07-18T00:55:42.889Z" }, + { url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload-time = "2025-07-18T00:55:47.069Z" }, + { url = "https://files.pythonhosted.org/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload-time = "2025-07-18T00:55:53.069Z" }, + { url = "https://files.pythonhosted.org/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload-time = "2025-07-18T00:55:57.714Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload-time = "2025-07-18T00:56:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" }, + { url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cc/ce4939f4b316457a083dc5718b3982801e8c33f921b3c98e7a93b7c7491f/pyarrow-21.0.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a7f6524e3747e35f80744537c78e7302cd41deee8baa668d56d55f77d9c464b3", size = 31211248, upload-time = "2025-07-18T00:56:59.7Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c2/7a860931420d73985e2f340f06516b21740c15b28d24a0e99a900bb27d2b/pyarrow-21.0.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:203003786c9fd253ebcafa44b03c06983c9c8d06c3145e37f1b76a1f317aeae1", size = 32676896, upload-time = "2025-07-18T00:57:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/197f989b9a75e59b4ca0db6a13c56f19a0ad8a298c68da9cc28145e0bb97/pyarrow-21.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b4d97e297741796fead24867a8dabf86c87e4584ccc03167e4a811f50fdf74d", size = 41067862, upload-time = "2025-07-18T00:57:07.587Z" }, + { url = "https://files.pythonhosted.org/packages/fa/82/6ecfa89487b35aa21accb014b64e0a6b814cc860d5e3170287bf5135c7d8/pyarrow-21.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:898afce396b80fdda05e3086b4256f8677c671f7b1d27a6976fa011d3fd0a86e", size = 42747508, upload-time = "2025-07-18T00:57:13.917Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b7/ba252f399bbf3addc731e8643c05532cf32e74cebb5e32f8f7409bc243cf/pyarrow-21.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:067c66ca29aaedae08218569a114e413b26e742171f526e828e1064fcdec13f4", size = 43345293, upload-time = "2025-07-18T00:57:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/a20819795bd702b9486f536a8eeb70a6aa64046fce32071c19ec8230dbaa/pyarrow-21.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0c4e75d13eb76295a49e0ea056eb18dbd87d81450bfeb8afa19a7e5a75ae2ad7", size = 45060670, upload-time = "2025-07-18T00:57:24.477Z" }, + { url = "https://files.pythonhosted.org/packages/10/15/6b30e77872012bbfe8265d42a01d5b3c17ef0ac0f2fae531ad91b6a6c02e/pyarrow-21.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdc4c17afda4dab2a9c0b79148a43a7f4e1094916b3e18d8975bfd6d6d52241f", size = 26227521, upload-time = "2025-07-18T00:57:29.119Z" }, +] + +[[package]] +name = "pyarrow" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/01/33/ffd9c3eb087fa41dd79c3cf20c4c0ae3cdb877c4f8e1107a446006344924/pyarrow-23.0.0.tar.gz", hash = "sha256:180e3150e7edfcd182d3d9afba72f7cf19839a497cc76555a8dce998a8f67615", size = 1167185, upload-time = "2026-01-18T16:19:42.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/2f/23e042a5aa99bcb15e794e14030e8d065e00827e846e53a66faec73c7cd6/pyarrow-23.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cbdc2bf5947aa4d462adcf8453cf04aee2f7932653cb67a27acd96e5e8528a67", size = 34281861, upload-time = "2026-01-18T16:13:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/1651933f504b335ec9cd8f99463718421eb08d883ed84f0abd2835a16cad/pyarrow-23.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:4d38c836930ce15cd31dce20114b21ba082da231c884bdc0a7b53e1477fe7f07", size = 35825067, upload-time = "2026-01-18T16:13:42.549Z" }, + { url = "https://files.pythonhosted.org/packages/84/ec/d6fceaec050c893f4e35c0556b77d4cc9973fcc24b0a358a5781b1234582/pyarrow-23.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4222ff8f76919ecf6c716175a0e5fddb5599faeed4c56d9ea41a2c42be4998b2", size = 44458539, upload-time = "2026-01-18T16:13:52.975Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/369f134d652b21db62fe3ec1c5c2357e695f79eb67394b8a93f3a2b2cffa/pyarrow-23.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:87f06159cbe38125852657716889296c83c37b4d09a5e58f3d10245fd1f69795", size = 47535889, upload-time = "2026-01-18T16:14:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/a3/95/f37b6a252fdbf247a67a78fb3f61a529fe0600e304c4d07741763d3522b1/pyarrow-23.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1675c374570d8b91ea6d4edd4608fa55951acd44e0c31bd146e091b4005de24f", size = 48157777, upload-time = "2026-01-18T16:14:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/fb94923108c9c6415dab677cf1f066d3307798eafc03f9a65ab4abc61056/pyarrow-23.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:247374428fde4f668f138b04031a7e7077ba5fa0b5b1722fdf89a017bf0b7ee0", size = 50580441, upload-time = "2026-01-18T16:14:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/ae/78/897ba6337b517fc8e914891e1bd918da1c4eb8e936a553e95862e67b80f6/pyarrow-23.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:de53b1bd3b88a2ee93c9af412c903e57e738c083be4f6392288294513cd8b2c1", size = 27530028, upload-time = "2026-01-18T16:14:27.353Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c0/57fe251102ca834fee0ef69a84ad33cc0ff9d5dfc50f50b466846356ecd7/pyarrow-23.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5574d541923efcbfdf1294a2746ae3b8c2498a2dc6cd477882f6f4e7b1ac08d3", size = 34276762, upload-time = "2026-01-18T16:14:34.128Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4e/24130286548a5bc250cbed0b6bbf289a2775378a6e0e6f086ae8c68fc098/pyarrow-23.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:2ef0075c2488932e9d3c2eb3482f9459c4be629aa673b725d5e3cf18f777f8e4", size = 35821420, upload-time = "2026-01-18T16:14:40.699Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/a869e8529d487aa2e842d6c8865eb1e2c9ec33ce2786eb91104d2c3e3f10/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:65666fc269669af1ef1c14478c52222a2aa5c907f28b68fb50a203c777e4f60c", size = 44457412, upload-time = "2026-01-18T16:14:49.051Z" }, + { url = "https://files.pythonhosted.org/packages/36/81/1de4f0edfa9a483bbdf0082a05790bd6a20ed2169ea12a65039753be3a01/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4d85cb6177198f3812db4788e394b757223f60d9a9f5ad6634b3e32be1525803", size = 47534285, upload-time = "2026-01-18T16:14:56.748Z" }, + { url = "https://files.pythonhosted.org/packages/f2/04/464a052d673b5ece074518f27377861662449f3c1fdb39ce740d646fd098/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1a9ff6fa4141c24a03a1a434c63c8fa97ce70f8f36bccabc18ebba905ddf0f17", size = 48157913, upload-time = "2026-01-18T16:15:05.114Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1b/32a4de9856ee6688c670ca2def588382e573cce45241a965af04c2f61687/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:84839d060a54ae734eb60a756aeacb62885244aaa282f3c968f5972ecc7b1ecc", size = 50582529, upload-time = "2026-01-18T16:15:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/d6581f03e9b9e44ea60b52d1750ee1a7678c484c06f939f45365a45f7eef/pyarrow-23.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a149a647dbfe928ce8830a713612aa0b16e22c64feac9d1761529778e4d4eaa5", size = 27542646, upload-time = "2026-01-18T16:15:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bd/c861d020831ee57609b73ea721a617985ece817684dc82415b0bc3e03ac3/pyarrow-23.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5961a9f646c232697c24f54d3419e69b4261ba8a8b66b0ac54a1851faffcbab8", size = 34189116, upload-time = "2026-01-18T16:15:28.054Z" }, + { url = "https://files.pythonhosted.org/packages/8c/23/7725ad6cdcbaf6346221391e7b3eecd113684c805b0a95f32014e6fa0736/pyarrow-23.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:632b3e7c3d232f41d64e1a4a043fb82d44f8a349f339a1188c6a0dd9d2d47d8a", size = 35803831, upload-time = "2026-01-18T16:15:33.798Z" }, + { url = "https://files.pythonhosted.org/packages/57/06/684a421543455cdc2944d6a0c2cc3425b028a4c6b90e34b35580c4899743/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:76242c846db1411f1d6c2cc3823be6b86b40567ee24493344f8226ba34a81333", size = 44436452, upload-time = "2026-01-18T16:15:41.598Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6f/8f9eb40c2328d66e8b097777ddcf38494115ff9f1b5bc9754ba46991191e/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b73519f8b52ae28127000986bf228fda781e81d3095cd2d3ece76eb5cf760e1b", size = 47557396, upload-time = "2026-01-18T16:15:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/10/6e/f08075f1472e5159553501fde2cc7bc6700944bdabe49a03f8a035ee6ccd/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:068701f6823449b1b6469120f399a1239766b117d211c5d2519d4ed5861f75de", size = 48147129, upload-time = "2026-01-18T16:16:00.299Z" }, + { url = "https://files.pythonhosted.org/packages/7d/82/d5a680cd507deed62d141cc7f07f7944a6766fc51019f7f118e4d8ad0fb8/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1801ba947015d10e23bca9dd6ef5d0e9064a81569a89b6e9a63b59224fd060df", size = 50596642, upload-time = "2026-01-18T16:16:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/a9/26/4f29c61b3dce9fa7780303b86895ec6a0917c9af927101daaaf118fbe462/pyarrow-23.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:52265266201ec25b6839bf6bd4ea918ca6d50f31d13e1cf200b4261cd11dc25c", size = 27660628, upload-time = "2026-01-18T16:16:15.28Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/564db447d083ec7ff93e0a883a597d2f214e552823bfc178a2d0b1f2c257/pyarrow-23.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:ad96a597547af7827342ffb3c503c8316e5043bb09b47a84885ce39394c96e00", size = 34184630, upload-time = "2026-01-18T16:16:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3a/3999daebcb5e6119690c92a621c4d78eef2ffba7a0a1b56386d2875fcd77/pyarrow-23.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:b9edf990df77c2901e79608f08c13fbde60202334a4fcadb15c1f57bf7afee43", size = 35796820, upload-time = "2026-01-18T16:16:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/39195233056c6a8d0976d7d1ac1cd4fe21fb0ec534eca76bc23ef3f60e11/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:36d1b5bc6ddcaff0083ceec7e2561ed61a51f49cce8be079ee8ed406acb6fdef", size = 44438735, upload-time = "2026-01-18T16:16:38.79Z" }, + { url = "https://files.pythonhosted.org/packages/2c/41/6a7328ee493527e7afc0c88d105ecca69a3580e29f2faaeac29308369fd7/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4292b889cd224f403304ddda8b63a36e60f92911f89927ec8d98021845ea21be", size = 47557263, upload-time = "2026-01-18T16:16:46.248Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ee/34e95b21ee84db494eae60083ddb4383477b31fb1fd19fd866d794881696/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dfd9e133e60eaa847fd80530a1b89a052f09f695d0b9c34c235ea6b2e0924cf7", size = 48153529, upload-time = "2026-01-18T16:16:53.412Z" }, + { url = "https://files.pythonhosted.org/packages/52/88/8a8d83cea30f4563efa1b7bf51d241331ee5cd1b185a7e063f5634eca415/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832141cc09fac6aab1cd3719951d23301396968de87080c57c9a7634e0ecd068", size = 50598851, upload-time = "2026-01-18T16:17:01.133Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4c/2929c4be88723ba025e7b3453047dc67e491c9422965c141d24bab6b5962/pyarrow-23.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:7a7d067c9a88faca655c71bcc30ee2782038d59c802d57950826a07f60d83c4c", size = 27577747, upload-time = "2026-01-18T16:18:02.413Z" }, + { url = "https://files.pythonhosted.org/packages/64/52/564a61b0b82d72bd68ec3aef1adda1e3eba776f89134b9ebcb5af4b13cb6/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ce9486e0535a843cf85d990e2ec5820a47918235183a5c7b8b97ed7e92c2d47d", size = 34446038, upload-time = "2026-01-18T16:17:07.861Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/232d4f9855fd1de0067c8a7808a363230d223c83aeee75e0fe6eab851ba9/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:075c29aeaa685fd1182992a9ed2499c66f084ee54eea47da3eb76e125e06064c", size = 35921142, upload-time = "2026-01-18T16:17:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/60af606a3748367b906bb82d41f0032e059f075444445d47e32a7ff1df62/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:799965a5379589510d888be3094c2296efd186a17ca1cef5b77703d4d5121f53", size = 44490374, upload-time = "2026-01-18T16:17:23.93Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/7731543050a678ea3a413955a2d5d80d2a642f270aa57a3cb7d5a86e3f46/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ef7cac8fe6fccd8b9e7617bfac785b0371a7fe26af59463074e4882747145d40", size = 47527896, upload-time = "2026-01-18T16:17:33.393Z" }, + { url = "https://files.pythonhosted.org/packages/5a/90/f3342553b7ac9879413aed46500f1637296f3c8222107523a43a1c08b42a/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15a414f710dc927132dd67c361f78c194447479555af57317066ee5116b90e9e", size = 48210401, upload-time = "2026-01-18T16:17:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/f3/da/9862ade205ecc46c172b6ce5038a74b5151c7401e36255f15975a45878b2/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e0d2e6915eca7d786be6a77bf227fbc06d825a75b5b5fe9bcbef121dec32685", size = 50579677, upload-time = "2026-01-18T16:17:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4c/f11f371f5d4740a5dafc2e11c76bcf42d03dfdb2d68696da97de420b6963/pyarrow-23.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4b317ea6e800b5704e5e5929acb6e2dc13e9276b708ea97a39eb8b345aa2658b", size = 27631889, upload-time = "2026-01-18T16:17:56.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/15aec78bcf43a0c004067bd33eb5352836a29a49db8581fc56f2b6ca88b7/pyarrow-23.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:20b187ed9550d233a872074159f765f52f9d92973191cd4b93f293a19efbe377", size = 34213265, upload-time = "2026-01-18T16:18:07.904Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/deb2c594bbba41c37c5d9aa82f510376998352aa69dfcb886cb4b18ad80f/pyarrow-23.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:18ec84e839b493c3886b9b5e06861962ab4adfaeb79b81c76afbd8d84c7d5fda", size = 35819211, upload-time = "2026-01-18T16:18:13.94Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/ee82af693cb7b5b2b74f6524cdfede0e6ace779d7720ebca24d68b57c36b/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e438dd3f33894e34fd02b26bd12a32d30d006f5852315f611aa4add6c7fab4bc", size = 44502313, upload-time = "2026-01-18T16:18:20.367Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/95c61ad82236495f3c31987e85135926ba3ec7f3819296b70a68d8066b49/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a244279f240c81f135631be91146d7fa0e9e840e1dfed2aba8483eba25cd98e6", size = 47585886, upload-time = "2026-01-18T16:18:27.544Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6e/a72d901f305201802f016d015de1e05def7706fff68a1dedefef5dc7eff7/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4692e83e42438dba512a570c6eaa42be2f8b6c0f492aea27dec54bdc495103a", size = 48207055, upload-time = "2026-01-18T16:18:35.425Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/5de029c537630ca18828db45c30e2a78da03675a70ac6c3528203c416fe3/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae7f30f898dfe44ea69654a35c93e8da4cef6606dc4c72394068fd95f8e9f54a", size = 50619812, upload-time = "2026-01-18T16:18:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/59/8d/2af846cd2412e67a087f5bda4a8e23dfd4ebd570f777db2e8686615dafc1/pyarrow-23.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:5b86bb649e4112fb0614294b7d0a175c7513738876b89655605ebb87c804f861", size = 28263851, upload-time = "2026-01-18T16:19:38.567Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7f/caab863e587041156f6786c52e64151b7386742c8c27140f637176e9230e/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ebc017d765d71d80a3f8584ca0566b53e40464586585ac64176115baa0ada7d3", size = 34463240, upload-time = "2026-01-18T16:18:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fa/3a5b8c86c958e83622b40865e11af0857c48ec763c11d472c87cd518283d/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:0800cc58a6d17d159df823f87ad66cefebf105b982493d4bad03ee7fab84b993", size = 35935712, upload-time = "2026-01-18T16:18:55.626Z" }, + { url = "https://files.pythonhosted.org/packages/c5/08/17a62078fc1a53decb34a9aa79cf9009efc74d63d2422e5ade9fed2f99e3/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a7c68c722da9bb5b0f8c10e3eae71d9825a4b429b40b32709df5d1fa55beb3d", size = 44503523, upload-time = "2026-01-18T16:19:03.958Z" }, + { url = "https://files.pythonhosted.org/packages/cc/70/84d45c74341e798aae0323d33b7c39194e23b1abc439ceaf60a68a7a969a/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:bd5556c24622df90551063ea41f559b714aa63ca953db884cfb958559087a14e", size = 47542490, upload-time = "2026-01-18T16:19:11.208Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/d1274b0e6f19e235de17441e53224f4716574b2ca837022d55702f24d71d/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54810f6e6afc4ffee7c2e0051b61722fbea9a4961b46192dcfae8ea12fa09059", size = 48233605, upload-time = "2026-01-18T16:19:19.544Z" }, + { url = "https://files.pythonhosted.org/packages/39/07/e4e2d568cb57543d84482f61e510732820cddb0f47c4bb7df629abfed852/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:14de7d48052cf4b0ed174533eafa3cfe0711b8076ad70bede32cf59f744f0d7c", size = 50603979, upload-time = "2026-01-18T16:19:26.717Z" }, + { url = "https://files.pythonhosted.org/packages/72/9c/47693463894b610f8439b2e970b82ef81e9599c757bf2049365e40ff963c/pyarrow-23.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:427deac1f535830a744a4f04a6ac183a64fcac4341b3f618e693c41b7b98d2b0", size = 28338905, upload-time = "2026-01-18T16:19:32.93Z" }, +] + [[package]] name = "pydantic" version = "2.12.5" @@ -5118,24 +5262,24 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.51.0" +version = "2.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/9f/094bbb6be5cf218ab6712c6528310687f3d3fe8818249fcfe1d74192f7c5/sentry_sdk-2.51.0.tar.gz", hash = "sha256:b89d64577075fd8c13088bc3609a2ce77a154e5beb8cba7cc16560b0539df4f7", size = 407447, upload-time = "2026-01-28T10:29:50.962Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/da/df379404d484ca9dede4ad8abead5de828cdcff35623cd44f0351cf6869c/sentry_sdk-2.51.0-py2.py3-none-any.whl", hash = "sha256:e21016d318a097c2b617bb980afd9fc737e1efc55f9b4f0cdc819982c9717d5f", size = 431426, upload-time = "2026-01-28T10:29:48.868Z" }, + { url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" }, ] [[package]] name = "setuptools" -version = "80.10.2" +version = "82.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] [[package]] @@ -5191,7 +5335,7 @@ dependencies = [ { name = "absl-py", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "grpcio" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -5454,7 +5598,7 @@ resolution-markers = [ dependencies = [ { name = "cuda-bindings", marker = "python_full_version >= '3.10' and platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "fsspec", version = "2026.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "fsspec", version = "2026.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "jinja2", marker = "python_full_version >= '3.10'" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -5479,6 +5623,10 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, ] wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/ea/304cf7afb744aa626fa9855245526484ee55aba610d9973a0521c552a843/torch-2.10.0-1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:c37fc46eedd9175f9c81814cc47308f1b42cfe4987e532d4b423d23852f2bf63", size = 79411450, upload-time = "2026-02-06T17:37:35.75Z" }, + { url = "https://files.pythonhosted.org/packages/25/d8/9e6b8e7df981a1e3ea3907fd5a74673e791da483e8c307f0b6ff012626d0/torch-2.10.0-1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:f699f31a236a677b3118bc0a3ef3d89c0c29b5ec0b20f4c4bf0b110378487464", size = 79423460, upload-time = "2026-02-06T17:37:39.657Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/0b295dd8d199ef71e6f176f576473d645d41357b7b8aa978cc6b042575df/torch-2.10.0-1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6abb224c2b6e9e27b592a1c0015c33a504b00a0e0938f1499f7f514e9b7bfb5c", size = 79498197, upload-time = "2026-02-06T17:37:27.627Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1b/af5fccb50c341bd69dc016769503cb0857c1423fbe9343410dfeb65240f2/torch-2.10.0-1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7350f6652dfd761f11f9ecb590bfe95b573e2961f7a242eccb3c8e78348d26fe", size = 79498248, upload-time = "2026-02-06T17:37:31.982Z" }, { url = "https://files.pythonhosted.org/packages/0c/1a/c61f36cfd446170ec27b3a4984f072fd06dab6b5d7ce27e11adb35d6c838/torch-2.10.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5276fa790a666ee8becaffff8acb711922252521b28fbce5db7db5cf9cb2026d", size = 145992962, upload-time = "2026-01-21T16:24:14.04Z" }, { url = "https://files.pythonhosted.org/packages/b5/60/6662535354191e2d1555296045b63e4279e5a9dbad49acf55a5d38655a39/torch-2.10.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:aaf663927bcd490ae971469a624c322202a2a1e68936eb952535ca4cd3b90444", size = 915599237, upload-time = "2026-01-21T16:23:25.497Z" }, { url = "https://files.pythonhosted.org/packages/40/b8/66bbe96f0d79be2b5c697b2e0b187ed792a15c6c4b8904613454651db848/torch-2.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:a4be6a2a190b32ff5c8002a0977a25ea60e64f7ba46b1be37093c141d9c49aeb", size = 113720931, upload-time = "2026-01-21T16:24:23.743Z" }, @@ -5752,26 +5900,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.14" +version = "0.0.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/57/22c3d6bf95c2229120c49ffc2f0da8d9e8823755a1c3194da56e51f1cc31/ty-0.0.14.tar.gz", hash = "sha256:a691010565f59dd7f15cf324cdcd1d9065e010c77a04f887e1ea070ba34a7de2", size = 5036573, upload-time = "2026-01-27T00:57:31.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/25/257602d316b9333089b688a7a11b33ebc660b74e8dacf400dc3dfdea1594/ty-0.0.15.tar.gz", hash = "sha256:4f9a5b8df208c62dba56e91b93bed8b5bb714839691b8cff16d12c983bfa1174", size = 5101936, upload-time = "2026-02-05T01:06:34.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/cb/cc6d1d8de59beb17a41f9a614585f884ec2d95450306c173b3b7cc090d2e/ty-0.0.14-py3-none-linux_armv6l.whl", hash = "sha256:32cf2a7596e693094621d3ae568d7ee16707dce28c34d1762947874060fdddaa", size = 10034228, upload-time = "2026-01-27T00:57:53.133Z" }, - { url = "https://files.pythonhosted.org/packages/f3/96/dd42816a2075a8f31542296ae687483a8d047f86a6538dfba573223eaf9a/ty-0.0.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f971bf9805f49ce8c0968ad53e29624d80b970b9eb597b7cbaba25d8a18ce9a2", size = 9939162, upload-time = "2026-01-27T00:57:43.857Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b4/73c4859004e0f0a9eead9ecb67021438b2e8e5fdd8d03e7f5aca77623992/ty-0.0.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:45448b9e4806423523268bc15e9208c4f3f2ead7c344f615549d2e2354d6e924", size = 9418661, upload-time = "2026-01-27T00:58:03.411Z" }, - { url = "https://files.pythonhosted.org/packages/58/35/839c4551b94613db4afa20ee555dd4f33bfa7352d5da74c5fa416ffa0fd2/ty-0.0.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee94a9b747ff40114085206bdb3205a631ef19a4d3fb89e302a88754cbbae54c", size = 9837872, upload-time = "2026-01-27T00:57:23.718Z" }, - { url = "https://files.pythonhosted.org/packages/41/2b/bbecf7e2faa20c04bebd35fc478668953ca50ee5847ce23e08acf20ea119/ty-0.0.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6756715a3c33182e9ab8ffca2bb314d3c99b9c410b171736e145773ee0ae41c3", size = 9848819, upload-time = "2026-01-27T00:57:58.501Z" }, - { url = "https://files.pythonhosted.org/packages/be/60/3c0ba0f19c0f647ad9d2b5b5ac68c0f0b4dc899001bd53b3a7537fb247a2/ty-0.0.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89d0038a2f698ba8b6fec5cf216a4e44e2f95e4a5095a8c0f57fe549f87087c2", size = 10324371, upload-time = "2026-01-27T00:57:29.291Z" }, - { url = "https://files.pythonhosted.org/packages/24/32/99d0a0b37d0397b0a989ffc2682493286aa3bc252b24004a6714368c2c3d/ty-0.0.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c64a83a2d669b77f50a4957039ca1450626fb474619f18f6f8a3eb885bf7544", size = 10865898, upload-time = "2026-01-27T00:57:33.542Z" }, - { url = "https://files.pythonhosted.org/packages/1a/88/30b583a9e0311bb474269cfa91db53350557ebec09002bfc3fb3fc364e8c/ty-0.0.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:242488bfb547ef080199f6fd81369ab9cb638a778bb161511d091ffd49c12129", size = 10555777, upload-time = "2026-01-27T00:58:05.853Z" }, - { url = "https://files.pythonhosted.org/packages/cd/a2/cb53fb6325dcf3d40f2b1d0457a25d55bfbae633c8e337bde8ec01a190eb/ty-0.0.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4790c3866f6c83a4f424fc7d09ebdb225c1f1131647ba8bdc6fcdc28f09ed0ff", size = 10412913, upload-time = "2026-01-27T00:57:38.834Z" }, - { url = "https://files.pythonhosted.org/packages/42/8f/f2f5202d725ed1e6a4e5ffaa32b190a1fe70c0b1a2503d38515da4130b4c/ty-0.0.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:950f320437f96d4ea9a2332bbfb5b68f1c1acd269ebfa4c09b6970cc1565bd9d", size = 9837608, upload-time = "2026-01-27T00:57:55.898Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/59a2a0521640c489dafa2c546ae1f8465f92956fede18660653cce73b4c5/ty-0.0.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a0ec3ee70d83887f86925bbc1c56f4628bd58a0f47f6f32ddfe04e1f05466df", size = 9884324, upload-time = "2026-01-27T00:57:46.786Z" }, - { url = "https://files.pythonhosted.org/packages/03/95/8d2a49880f47b638743212f011088552ecc454dd7a665ddcbdabea25772a/ty-0.0.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1a4e6b6da0c58b34415955279eff754d6206b35af56a18bb70eb519d8d139ef", size = 10033537, upload-time = "2026-01-27T00:58:01.149Z" }, - { url = "https://files.pythonhosted.org/packages/e9/40/4523b36f2ce69f92ccf783855a9e0ebbbd0f0bb5cdce6211ee1737159ed3/ty-0.0.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dc04384e874c5de4c5d743369c277c8aa73d1edea3c7fc646b2064b637db4db3", size = 10495910, upload-time = "2026-01-27T00:57:26.691Z" }, - { url = "https://files.pythonhosted.org/packages/08/d5/655beb51224d1bfd4f9ddc0bb209659bfe71ff141bcf05c418ab670698f0/ty-0.0.14-py3-none-win32.whl", hash = "sha256:b20e22cf54c66b3e37e87377635da412d9a552c9bf4ad9fc449fed8b2e19dad2", size = 9507626, upload-time = "2026-01-27T00:57:41.43Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d9/c569c9961760e20e0a4bc008eeb1415754564304fd53997a371b7cf3f864/ty-0.0.14-py3-none-win_amd64.whl", hash = "sha256:e312ff9475522d1a33186657fe74d1ec98e4a13e016d66f5758a452c90ff6409", size = 10437980, upload-time = "2026-01-27T00:57:36.422Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/186829654f5bfd9a028f6648e9caeb11271960a61de97484627d24443f91/ty-0.0.14-py3-none-win_arm64.whl", hash = "sha256:b6facdbe9b740cb2c15293a1d178e22ffc600653646452632541d01c36d5e378", size = 9885831, upload-time = "2026-01-27T00:57:49.747Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c5/35626e732b79bf0e6213de9f79aff59b5f247c0a1e3ce0d93e675ab9b728/ty-0.0.15-py3-none-linux_armv6l.whl", hash = "sha256:68e092458516c61512dac541cde0a5e4e5842df00b4e81881ead8f745ddec794", size = 10138374, upload-time = "2026-02-05T01:07:03.804Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8a/48fd81664604848f79d03879b3ca3633762d457a069b07e09fb1b87edd6e/ty-0.0.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79f2e75289eae3cece94c51118b730211af4ba5762906f52a878041b67e54959", size = 9947858, upload-time = "2026-02-05T01:06:47.453Z" }, + { url = "https://files.pythonhosted.org/packages/b6/85/c1ac8e97bcd930946f4c94db85b675561d590b4e72703bf3733419fc3973/ty-0.0.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:112a7b26e63e48cc72c8c5b03227d1db280cfa57a45f2df0e264c3a016aa8c3c", size = 9443220, upload-time = "2026-02-05T01:06:44.98Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d9/244bc02599d950f7a4298fbc0c1b25cc808646b9577bdf7a83470b2d1cec/ty-0.0.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f62a2644972975a657d9dc867bf901235cde51e8d24c20311067e7afd44a56", size = 9949976, upload-time = "2026-02-05T01:07:01.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ab/3a0daad66798c91a33867a3ececf17d314ac65d4ae2bbbd28cbfde94da63/ty-0.0.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e48b42be2d257317c85b78559233273b655dd636fc61e7e1d69abd90fd3cba4", size = 9965918, upload-time = "2026-02-05T01:06:54.283Z" }, + { url = "https://files.pythonhosted.org/packages/39/4e/e62b01338f653059a7c0cd09d1a326e9a9eedc351a0f0de9db0601658c3d/ty-0.0.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27dd5b52a421e6871c5bfe9841160331b60866ed2040250cb161886478ab3e4f", size = 10424943, upload-time = "2026-02-05T01:07:08.777Z" }, + { url = "https://files.pythonhosted.org/packages/65/b5/7aa06655ce69c0d4f3e845d2d85e79c12994b6d84c71699cfb437e0bc8cf/ty-0.0.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76b85c9ec2219e11c358a7db8e21b7e5c6674a1fb9b6f633836949de98d12286", size = 10964692, upload-time = "2026-02-05T01:06:37.103Z" }, + { url = "https://files.pythonhosted.org/packages/13/04/36fdfe1f3c908b471e246e37ce3d011175584c26d3853e6c5d9a0364564c/ty-0.0.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e8204c61d8ede4f21f2975dce74efdb80fafb2fae1915c666cceb33ea3c90b", size = 10692225, upload-time = "2026-02-05T01:06:49.714Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af87c3be7c944bb4d6609d6c63e4594944b0028c7bd490a525a82b88fe010d6d", size = 10516776, upload-time = "2026-02-05T01:06:52.047Z" }, + { url = "https://files.pythonhosted.org/packages/56/75/66852d7e004f859839c17ffe1d16513c1e7cc04bcc810edb80ca022a9124/ty-0.0.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50dccf7398505e5966847d366c9e4c650b8c225411c2a68c32040a63b9521eea", size = 9928828, upload-time = "2026-02-05T01:06:56.647Z" }, + { url = "https://files.pythonhosted.org/packages/65/72/96bc16c7b337a3ef358fd227b3c8ef0c77405f3bfbbfb59ee5915f0d9d71/ty-0.0.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bd797b8f231a4f4715110259ad1ad5340a87b802307f3e06d92bfb37b858a8f3", size = 9978960, upload-time = "2026-02-05T01:06:29.567Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/d2e316a35b626de2227f832cd36d21205e4f5d96fd036a8af84c72ecec1b/ty-0.0.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9deb7f20e18b25440a9aa4884f934ba5628ef456dbde91819d5af1a73da48af3", size = 10135903, upload-time = "2026-02-05T01:06:59.256Z" }, + { url = "https://files.pythonhosted.org/packages/02/d3/b617a79c9dad10c888d7c15cd78859e0160b8772273637b9c4241a049491/ty-0.0.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7b31b3de031255b90a5f4d9cb3d050feae246067c87130e5a6861a8061c71754", size = 10615879, upload-time = "2026-02-05T01:07:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/fb/b0/2652a73c71c77296a6343217063f05745da60c67b7e8a8e25f2064167fce/ty-0.0.15-py3-none-win32.whl", hash = "sha256:9362c528ceb62c89d65c216336d28d500bc9f4c10418413f63ebc16886e16cc1", size = 9578058, upload-time = "2026-02-05T01:06:42.928Z" }, + { url = "https://files.pythonhosted.org/packages/84/6e/08a4aedebd2a6ce2784b5bc3760e43d1861f1a184734a78215c2d397c1df/ty-0.0.15-py3-none-win_amd64.whl", hash = "sha256:4db040695ae67c5524f59cb8179a8fa277112e69042d7dfdac862caa7e3b0d9c", size = 10457112, upload-time = "2026-02-05T01:06:39.885Z" }, + { url = "https://files.pythonhosted.org/packages/b3/be/1991f2bc12847ae2d4f1e3ac5dcff8bb7bc1261390645c0755bb55616355/ty-0.0.15-py3-none-win_arm64.whl", hash = "sha256:e5a98d4119e77d6136461e16ae505f8f8069002874ab073de03fbcb1a5e8bf25", size = 9937490, upload-time = "2026-02-05T01:06:32.388Z" }, ] [[package]] @@ -5928,11 +6076,11 @@ wheels = [ [[package]] name = "types-setuptools" -version = "80.10.0.20260124" +version = "81.0.0.20260209" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/7e/116539b9610585e34771611e33c88a4c706491fa3565500f5a63139f8731/types_setuptools-80.10.0.20260124.tar.gz", hash = "sha256:1b86d9f0368858663276a0cbe5fe5a9722caf94b5acde8aba0399a6e90680f20", size = 43299, upload-time = "2026-01-24T03:18:39.527Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/57/f1f7992d6d7bded78d1f14dc23d59e87601920852bf10ece2325e49bacae/types_setuptools-81.0.0.20260209.tar.gz", hash = "sha256:2c2eb64499b41b672c387f6f45678a28d20a143a81b45a5c77acbfd4da0df3e1", size = 43201, upload-time = "2026-02-09T04:14:15.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/7f/016dc5cc718ec6ccaa84fb73ed409ef1c261793fd5e637cdfaa18beb40a9/types_setuptools-80.10.0.20260124-py3-none-any.whl", hash = "sha256:efed7e044f01adb9c2806c7a8e1b6aa3656b8e382379b53d5f26ee3db24d4c01", size = 64333, upload-time = "2026-01-24T03:18:38.344Z" }, + { url = "https://files.pythonhosted.org/packages/3f/87/90c9143af95850bdaf7eb0d47c59e5c3a8b55fc5a49aca0eb7f98cb964d5/types_setuptools-81.0.0.20260209-py3-none-any.whl", hash = "sha256:4facf71e3f953f8f5ac0020cd6c1b5e493aaff0183e85830bc34870b6abf8475", size = 64194, upload-time = "2026-02-09T04:14:14.278Z" }, ] [[package]] @@ -6029,37 +6177,36 @@ wheels = [ [[package]] name = "uv" -version = "0.9.29" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/97/71/8a5bf591f3d9674e0a9144567d2e0a16fd04a33b4ab8ecfc902f1551c709/uv-0.9.29.tar.gz", hash = "sha256:140422df01de34dc335bd29827ae6aec6ecb2b92c2ee8ed6bc6dbeee50ac2f4e", size = 3838234, upload-time = "2026-02-03T19:39:06.702Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/35/a8d744a2866d176a16c02ead8d277e0b02ae587a68c89cb2b5b9b8bcf602/uv-0.9.29-py3-none-linux_armv6l.whl", hash = "sha256:54fc0056a8f41b43e41c4c677632f751842f5d94b91dea4d547086448a8325eb", size = 21998377, upload-time = "2026-02-03T19:38:24.678Z" }, - { url = "https://files.pythonhosted.org/packages/8b/82/92b539e445c75706cbc8b9ac00291ee2923602e68109d73dffa9ab412257/uv-0.9.29-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66a5f5c5ecf62f32b8d71383760a422aa9a2a2798cbb6424fb25ccfa8fd53a81", size = 21032721, upload-time = "2026-02-03T19:38:44.791Z" }, - { url = "https://files.pythonhosted.org/packages/55/e8/0489cb87d25a9b06ec3b867fecfd32a9a054dcef8c889662c153d20bba3d/uv-0.9.29-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11aad2d15a9e78551f656886ce604810f872fa2452127216f8ff5d75febae26e", size = 19824587, upload-time = "2026-02-03T19:38:17.32Z" }, - { url = "https://files.pythonhosted.org/packages/ef/09/8e06484d3f1713170926b356913deb0cf25f14ba6c77d765afdbac33e07c/uv-0.9.29-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4f118141a84862b96f4a4f2bf5e2436f65a8b572706861e0d4585f4bc87fdac0", size = 21616388, upload-time = "2026-02-03T19:38:52.269Z" }, - { url = "https://files.pythonhosted.org/packages/04/da/0c5cfd9d0296c78968fb588ca5a018a6b0e0132bdf3b0fca712cd0ffa938/uv-0.9.29-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:ca5effa2b227a989f341197248551be00919d3dbd13e9d03fabd1af26a9f9d41", size = 21622407, upload-time = "2026-02-03T19:39:12.999Z" }, - { url = "https://files.pythonhosted.org/packages/e5/3f/7c14c282b3d258a274d382c0e03b13fafac99483590476ceb01ca54e2b9d/uv-0.9.29-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d227644f94c66abf82eb51f33cb03a3e2e50f00d502438bc2f0bae1f4ae0e5a5", size = 21585617, upload-time = "2026-02-03T19:38:21.272Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d9/4db58a2f5d311a0549d1f0855e1f650364265e743709ef81824cf86c7ae6/uv-0.9.29-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14a6c27f7c61ca1dc9c6edf53d39e9f289531873c8488ed24bd15e49353a485c", size = 22794114, upload-time = "2026-02-03T19:38:59.809Z" }, - { url = "https://files.pythonhosted.org/packages/8c/41/4d4df6dd7e88bea33557c3b6fd36e054e057cf8dfd64b8e97b4f40c8d170/uv-0.9.29-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5c99fd20ae5a98066c03e06a8f4c5a68e71acf8029d1ab7eba682f7166696b52", size = 24121009, upload-time = "2026-02-03T19:38:13.137Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ef/9a82a1bf3c5d23dd4ecf3c8778fc8ffc241e671fef519e3e7722884e93ba/uv-0.9.29-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113cbe21a39fa2cfbe146333141561e015a67dfaec7d12415c7ec6ff9f878754", size = 23655975, upload-time = "2026-02-03T19:38:28.713Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f5/6158eaf6558962ca6a7c17ecbe14a2434166d5a0dae9712aca16b8520f46/uv-0.9.29-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d36fe3f9de3a37f7d712ee51ebf42d97df7a00ec901b02b6306c7ebbab8c6a76", size = 22881973, upload-time = "2026-02-03T19:39:03.854Z" }, - { url = "https://files.pythonhosted.org/packages/7b/fa/e725329efb484997fd60018d62f931901f3d25a04b95278845c1ad25b00d/uv-0.9.29-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae09db1bbdad5c38c508876a5903a322951539146f14c7567448bdcdea67e1fe", size = 22760712, upload-time = "2026-02-03T19:38:33.372Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/8a2e4ad9a8024ceb10c04a9c386220d53107e6f3bff7a246fe36622b5342/uv-0.9.29-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:aaf650ddf20a6029a59c136eaeade720655c07bfbbd4e7867cc9b6167b0abae9", size = 21721267, upload-time = "2026-02-03T19:38:09.623Z" }, - { url = "https://files.pythonhosted.org/packages/3e/05/8a3b8a190b5ffb9b0d07d10f6f962e29e0f5aa4209415e78bf0514e2394a/uv-0.9.29-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:4095f5763c69d75f324d81e799d90c682f63f4789f7b8ad4297484262ecdeffd", size = 22426985, upload-time = "2026-02-03T19:38:48.4Z" }, - { url = "https://files.pythonhosted.org/packages/41/1d/af83aeebb75062c8539ffdeaa7474ff3c7acb6263d6d7ead28219c71f5d8/uv-0.9.29-py3-none-musllinux_1_1_i686.whl", hash = "sha256:52a6934cbbb3dd339c24e8de1cdd0d3239b82ce5e65289e0b13055009abf2bc1", size = 22051690, upload-time = "2026-02-03T19:39:09.552Z" }, - { url = "https://files.pythonhosted.org/packages/91/65/fe381859f237a5d2b271bc69215ebc5b87cbfd156ad901927921ef82b2e1/uv-0.9.29-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:367cb2a7ab2138b796caf5b402e343ef47f93329ae5d08a05d7bcfeca51b19e7", size = 22968942, upload-time = "2026-02-03T19:38:05.09Z" }, - { url = "https://files.pythonhosted.org/packages/80/04/155263d673c980da9b513673d9a61bb8a5a98547c8e42af3613881ca54e1/uv-0.9.29-py3-none-win32.whl", hash = "sha256:fcb17d9576598f536a04139beefd82187e84db3e6d11a16fa5507f5d3d414f28", size = 20890568, upload-time = "2026-02-03T19:38:40.928Z" }, - { url = "https://files.pythonhosted.org/packages/78/0a/450bd74385c4da3d83639946eaf39ca5bbcb69e73a0433d3bcc65af096d0/uv-0.9.29-py3-none-win_amd64.whl", hash = "sha256:b823c17132b851bf452e38f68e5dd39de9b433c39e2cd3aec2a1734b1594c295", size = 23465607, upload-time = "2026-02-03T19:38:37.411Z" }, - { url = "https://files.pythonhosted.org/packages/ad/2a/0d4a615f36d53a7cf1992351c395b17367783cface5afa5976db4c96675d/uv-0.9.29-py3-none-win_arm64.whl", hash = "sha256:22ab5e68d2d6a283a0a290e9b4a3ce53fef55f6ae197a5f6a58b7f4c605f21c8", size = 21911432, upload-time = "2026-02-03T19:38:55.987Z" }, +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/36/f7fe4de0ad81234ac43938fe39c6ba84595c6b3a1868d786a4d7ad19e670/uv-0.10.0.tar.gz", hash = "sha256:ad01dd614a4bb8eb732da31ade41447026427397c5ad171cc98bd59579ef57ea", size = 3854103, upload-time = "2026-02-05T20:57:55.248Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/69/33fb64aee6ba138b1aaf957e20778e94a8c23732e41cdf68e6176aa2cf4e/uv-0.10.0-py3-none-linux_armv6l.whl", hash = "sha256:38dc0ccbda6377eb94095688c38e5001b8b40dfce14b9654949c1f0b6aa889df", size = 21984662, upload-time = "2026-02-05T20:57:19.076Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5a/e3ff8a98cfbabc5c2d09bf304d2d9d2d7b2e7d60744241ac5ed762015e5c/uv-0.10.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a165582c1447691109d49d09dccb065d2a23852ff42bf77824ff169909aa85da", size = 21057249, upload-time = "2026-02-05T20:56:48.921Z" }, + { url = "https://files.pythonhosted.org/packages/ee/77/ec8f24f8d0f19c4fda0718d917bb78b9e6f02a4e1963b401f1c4f4614a54/uv-0.10.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aefea608971f4f23ac3dac2006afb8eb2b2c1a2514f5fee1fac18e6c45fd70c4", size = 19827174, upload-time = "2026-02-05T20:57:10.581Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/09b38b93208906728f591f66185a425be3acdb97c448460137d0e6ecb30a/uv-0.10.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:d4b621bcc5d0139502789dc299bae8bf55356d07b95cb4e57e50e2afcc5f43e1", size = 21629522, upload-time = "2026-02-05T20:57:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/89/f3/48d92c90e869331306979efaa29a44c3e7e8376ae343edc729df0d534dfb/uv-0.10.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:b4bea728a6b64826d0091f95f28de06dd2dc786384b3d336a90297f123b4da0e", size = 21614812, upload-time = "2026-02-05T20:56:58.103Z" }, + { url = "https://files.pythonhosted.org/packages/ff/43/d0dedfcd4fe6e36cabdbeeb43425cd788604db9d48425e7b659d0f7ba112/uv-0.10.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc0cc2a4bcf9efbff9a57e2aed21c2d4b5a7ec2cc0096e0c33d7b53da17f6a3b", size = 21577072, upload-time = "2026-02-05T20:57:45.455Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/b8c9320fd8d86f356e37505a02aa2978ed28f9c63b59f15933e98bce97e5/uv-0.10.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:070ca2f0e8c67ca9a8f70ce403c956b7ed9d51e0c2e9dbbcc4efa5e0a2483f79", size = 22829664, upload-time = "2026-02-05T20:57:22.689Z" }, + { url = "https://files.pythonhosted.org/packages/56/9c/2c36b30b05c74b2af0e663e0e68f1d10b91a02a145e19b6774c121120c0b/uv-0.10.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8070c66149c06f9b39092a06f593a2241345ea2b1d42badc6f884c2cc089a1b1", size = 23705815, upload-time = "2026-02-05T20:57:37.604Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/8c7fdb14ab72e26ca872e07306e496a6b8cf42353f9bf6251b015be7f535/uv-0.10.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db1d5390b3a624de672d7b0f9c9d8197693f3b2d3d9c4d9e34686dcbc34197a", size = 22890313, upload-time = "2026-02-05T20:57:26.35Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f8/5c152350b1a6d0af019801f91a1bdeac854c33deb36275f6c934f0113cb5/uv-0.10.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b46db718763bf742e986ebbc7a30ca33648957a0dcad34382970b992f5e900", size = 22769440, upload-time = "2026-02-05T20:56:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/980e5399c6f4943b81754be9b7deb87bd56430e035c507984e17267d6a97/uv-0.10.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:eb95d28590edd73b8fdd80c27d699c45c52f8305170c6a90b830caf7f36670a4", size = 21695296, upload-time = "2026-02-05T20:57:06.732Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e7/f44ad40275be2087b3910df4678ed62cf0c82eeb3375c4a35037a79747db/uv-0.10.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5871eef5046a81df3f1636a3d2b4ccac749c23c7f4d3a4bae5496cb2876a1814", size = 22424291, upload-time = "2026-02-05T20:57:49.067Z" }, + { url = "https://files.pythonhosted.org/packages/c2/81/31c0c0a8673140756e71a1112bf8f0fcbb48a4cf4587a7937f5bd55256b6/uv-0.10.0-py3-none-musllinux_1_1_i686.whl", hash = "sha256:1af0ec125a07edb434dfaa98969f6184c1313dbec2860c3c5ce2d533b257132a", size = 22109479, upload-time = "2026-02-05T20:57:02.258Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d1/2eb51bc233bad3d13ad64a0c280fd4d1ebebf5c2939b3900a46670fa2b91/uv-0.10.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:45909b9a734250da05b10101e0a067e01ffa2d94bbb07de4b501e3cee4ae0ff3", size = 22972087, upload-time = "2026-02-05T20:57:52.847Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f7/49987207b87b5c21e1f0e81c52892813e8cdf7e318b6373d6585773ebcdd/uv-0.10.0-py3-none-win32.whl", hash = "sha256:d5498851b1f07aa9c9af75578b2029a11743cb933d741f84dcbb43109a968c29", size = 20896746, upload-time = "2026-02-05T20:57:33.426Z" }, + { url = "https://files.pythonhosted.org/packages/80/b2/1370049596c6ff7fa1fe22fccf86a093982eac81017b8c8aff541d7263b2/uv-0.10.0-py3-none-win_amd64.whl", hash = "sha256:edd469425cd62bcd8c8cc0226c5f9043a94e37ed869da8268c80fdbfd3e5015e", size = 23433041, upload-time = "2026-02-05T20:57:41.41Z" }, + { url = "https://files.pythonhosted.org/packages/e3/76/1034c46244feafec2c274ac52b094f35d47c94cdb11461c24cf4be8a0c0c/uv-0.10.0-py3-none-win_arm64.whl", hash = "sha256:e90c509749b3422eebb54057434b7119892330d133b9690a88f8a6b0f3116be3", size = 21880261, upload-time = "2026-02-05T20:57:14.724Z" }, ] [[package]] name = "wcwidth" -version = "0.5.3" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/62/a7c072fbfefb2980a00f99ca994279cb9ecf310cb2e6b2a4d2a28fe192b3/wcwidth-0.5.3.tar.gz", hash = "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", size = 157587, upload-time = "2026-01-31T03:52:10.92Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/c1/d73f12f8cdb1891334a2ccf7389eed244d3941e74d80dd220badb937f3fb/wcwidth-0.5.3-py3-none-any.whl", hash = "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e", size = 92981, upload-time = "2026-01-31T03:52:09.14Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, ] [[package]] @@ -6269,16 +6416,15 @@ wheels = [ [[package]] name = "z3-solver" -version = "4.15.4.0" +version = "4.15.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/8e/0c8f17309549d2e5cde9a3ccefa6365437f1e7bafe71878eaf9478e47b18/z3_solver-4.15.4.0.tar.gz", hash = "sha256:928c29b58c4eb62106da51c1914f6a4a55d0441f8f48a81b9da07950434a8946", size = 5018600, upload-time = "2025-10-29T18:12:03.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/5d/810ba04f7e7f2f2e5f019dd75237d1a16b7388a0c72f7e532b27dde9f7e2/z3_solver-4.15.7.0.tar.gz", hash = "sha256:a26b91f861b6d13bb76f0ac568d3ef1c0a4801e70a135f80e66b49628565a460", size = 5071448, upload-time = "2026-02-09T01:08:40.767Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/33/a3d5d2eaeb0f7b3174d57d405437eabb2075d4d50bd9ea0957696c435c7b/z3_solver-4.15.4.0-py3-none-macosx_13_0_arm64.whl", hash = "sha256:407e825cc9211f95ef46bdc8d151bf630e7ab2d62a21d24cd74c09cc5b73f3aa", size = 37052538, upload-time = "2025-10-29T18:11:46.233Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/fd7ffac1551cd9f8d44fe41358f738be670fc4c24dfd514fab503f2cf3e7/z3_solver-4.15.4.0-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:00bd10c5a6a5f6112d3a9a810d0799227e52f76caa860dafa5e00966bb47eb13", size = 39807925, upload-time = "2025-10-29T18:11:49.81Z" }, - { url = "https://files.pythonhosted.org/packages/21/c9/bb51a96af0091324c81b803f16c49f719f9f6ea0b0bb52200f5c97ec4892/z3_solver-4.15.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e103a6f203f505b8b8b8e5c931cc407c95b61556512d4921c1ddc0b3f41b08e", size = 29268352, upload-time = "2025-10-29T18:11:53.032Z" }, - { url = "https://files.pythonhosted.org/packages/bf/2e/0b49f7e4e53817cfb09a0f6585012b782dfe0b666e8abefcb4fac0570606/z3_solver-4.15.4.0-py3-none-manylinux_2_34_aarch64.whl", hash = "sha256:62c7e9cbdd711932301f29919ad9158de9b2f58b4d281dd259bbcd0a2f408ba1", size = 27226534, upload-time = "2025-10-29T18:11:55.59Z" }, - { url = "https://files.pythonhosted.org/packages/26/91/33de49538444d4aafbe47415c450c2f9abab1733e1226f276b496672f46c/z3_solver-4.15.4.0-py3-none-win32.whl", hash = "sha256:be3bc916545c96ffbf89e00d07104ff14f78336e55db069177a1bfbcc01b269d", size = 13191672, upload-time = "2025-10-29T18:11:58.424Z" }, - { url = "https://files.pythonhosted.org/packages/03/d6/a0b135e4419df475177ae78fc93c422430b0fd8875649486f9a5989772e6/z3_solver-4.15.4.0-py3-none-win_amd64.whl", hash = "sha256:00e35b02632ed085ea8199fb230f6015e6fc40554a6680c097bd5f060e827431", size = 16259597, upload-time = "2025-10-29T18:12:01.14Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1b/d21f292b473c1c40bedf41d113577ae2bb7fcc715f54d42c10b7f2b3a186/z3_solver-4.15.7.0-py3-none-macosx_15_0_arm64.whl", hash = "sha256:a6c967677c67296a8b7c97dff68107f029c576a94cfb4abc9e08bf72e5499e5d", size = 36987369, upload-time = "2026-02-09T01:08:27.585Z" }, + { url = "https://files.pythonhosted.org/packages/77/36/132c3d03de2eed160fad123207c981507193b2621e05b2909563775e0ad9/z3_solver-4.15.7.0-py3-none-macosx_15_0_x86_64.whl", hash = "sha256:a9644e958252dfdbdae2f787a8192fe4b8c156e7cf7b0e00a6a59e896a27569d", size = 47560235, upload-time = "2026-02-09T01:08:30.415Z" }, + { url = "https://files.pythonhosted.org/packages/61/49/40b0ee7cd2425dfa05bde5776f6aa7e892460a5ca8016171204f9b2d42df/z3_solver-4.15.7.0-py3-none-win32.whl", hash = "sha256:2dd09ac8afde63035d9c0a63b23d448726e374ec588b67b5f5edce9d7e9b1a13", size = 13342998, upload-time = "2026-02-09T01:08:33.84Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ab/5a60c6ed712eb97749cd758162842cec771cfbe2c37ea43a251dc6fe583b/z3_solver-4.15.7.0-py3-none-win_amd64.whl", hash = "sha256:17f5ccea921d6a11bba5880281048c9f4a1e0c35f76e8ce69e72826c90c230bd", size = 16427563, upload-time = "2026-02-09T01:08:35.884Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1f/ea28f6b3dec9cbab32cf851b3a529c9fb8332300c7419a55ab68ef5b40ac/z3_solver-4.15.7.0-py3-none-win_arm64.whl", hash = "sha256:9bf1a350598bc92ece90220073fe47c0b0f8cbbeaaf62974de736bd79947f8bd", size = 15082309, upload-time = "2026-02-09T01:08:38.832Z" }, ] [[package]]