diff --git a/.amazonq/plan/compare_template_files.py b/.amazonq/plan/compare_template_files.py new file mode 100644 index 0000000..aeb083a --- /dev/null +++ b/.amazonq/plan/compare_template_files.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Reusable file comparison tool for template validation.""" + +import difflib +from pathlib import Path + +import typer + + +def compare_files(file1: Path, file2: Path, label1: str, label2: str) -> bool: + """Compare two files and show differences. Returns True if identical.""" + typer.echo(f"\n{'='*60}") + typer.echo(f"COMPARING: {file1.name}") + typer.echo(f"{label1}: {file1}") + typer.echo(f"{label2}: {file2}") + typer.echo(f"{'='*60}") + + if not file1.exists(): + typer.echo(f"❌ MISSING: {file1} does not exist") + return False + + if not file2.exists(): + typer.echo(f"❌ MISSING: {file2} does not exist") + return False + + content1 = file1.read_text().splitlines(keepends=True) + content2 = file2.read_text().splitlines(keepends=True) + + diff = list( + difflib.unified_diff( + content1, + content2, + fromfile=f"{label1}/{file1.name}", + tofile=f"{label2}/{file2.name}", + lineterm="", + ) + ) + + if not diff: + typer.echo("✅ FILES IDENTICAL") + return True + else: + typer.echo("❌ DIFFERENCES FOUND:") + for line in diff: + typer.echo(line) + return False + + +def main( + mode: str = typer.Argument(help="Compare template files or generated files (template|generated)"), + problem: str = typer.Option("invert_binary_tree", help="Problem name for comparison"), +): + """Compare files for template validation.""" + if mode not in ["template", "generated"]: + typer.echo(f"❌ ERROR: Invalid mode '{mode}'. Use 'template' or 'generated'") + return + + base_dir = Path(__file__).parent.parent.parent + + files_to_compare = ["solution.py", "tests.py", "README.md", "playground.ipynb", "__init__.py"] + + if mode == "template": + # Compare reference vs template source + dir1 = base_dir / "leetcode" / ".example" / problem + dir2 = base_dir / ".templates" / "leetcode" / ".example" / "{{cookiecutter.problem_name}}" + label1, label2 = "Reference", "Template" + typer.echo("TEMPLATE SOURCE ANALYSIS") + + elif mode == "generated": + # Compare reference vs currently generated + dir1 = base_dir / "leetcode" / ".example" / problem + dir2 = base_dir / "leetcode" / problem + label1, label2 = "Reference", "Generated" + typer.echo("GENERATED FILES VALIDATION") + + if not dir2.exists(): + typer.echo(f"\n❌ ERROR: Generated directory does not exist: {dir2}") + typer.echo(f"Run: make p-gen PROBLEM={problem}") + return + + typer.echo(f"{label1}: {dir1}") + typer.echo(f"{label2}: {dir2}") + + identical_count = 0 + for filename in files_to_compare: + file1 = dir1 / filename + file2 = dir2 / filename + if compare_files(file1, file2, label1, label2): + identical_count += 1 + + typer.echo(f"\n{'='*60}") + typer.echo(f"SUMMARY: {identical_count}/{len(files_to_compare)} files identical") + typer.echo("- ✅ = Files identical") + typer.echo("- ❌ = Differences found or missing files") + + +if __name__ == "__main__": + typer.run(main) diff --git a/.amazonq/plan/cookiecutter-template-plan.md b/.amazonq/plan/cookiecutter-template-plan.md new file mode 100644 index 0000000..8e18a7c --- /dev/null +++ b/.amazonq/plan/cookiecutter-template-plan.md @@ -0,0 +1,224 @@ +# Cookiecutter Template Modernization Plan + +## TASK PURPOSE & CRITICAL RULES + +**PURPOSE:** Update the cookiecutter template to generate files that exactly match the reference structure in `.templates/leetcode/.example/{{cookiecutter.problem_name}}/` + +**REFERENCE DIRECTORIES (NEVER MODIFY - THESE ARE EXAMPLES):** + +- `.templates/leetcode/.example/{{cookiecutter.problem_name}}/` - Shows what the template SHOULD generate +- `leetcode/.example/` - Generated file examples for comparison + +**ACTUAL TEMPLATE DIRECTORY (MODIFY THIS):** + +- `.templates/leetcode/{{cookiecutter.problem_name}}/` - The cookiecutter template files to update + +**WORKFLOW:** + +1. Look at `.templates/leetcode/.example/{{cookiecutter.problem_name}}/` to see target structure +2. Modify `.templates/leetcode/{{cookiecutter.problem_name}}/` to match the reference +3. Generate with `make p-gen` +4. Compare generated files vs reference with `make p-validate` + +**ERROR PREVENTION:** The template directory does NOT have `.example` in the path! + +## Analysis Summary + +**Target Structure**: `leetcode/.example/` contains the reference implementation +**Key Differences Found:** + +- `leetcode/.example/` has `__init__.py` files (missing in old template) +- `leetcode/.example/` uses modern Python syntax (`TreeNode | None` vs `Optional[TreeNode]`) +- `leetcode/.example/` follows project's coding standards more closely +- Template must generate files identical to `leetcode/.example/` structure + +## Implementation Plan + +### 0. Explicit File Content Analysis + +- **Tool**: `.amazonq/plan/compare_template_files.py` (already exists - no need to implement) +- **Usage**: + - `poetry run python .amazonq/plan/compare_template_files.py generated --problem=PROBLEM_NAME` - Compare generated files vs reference +- **Analysis**: Line-by-line diff of all file types +- **Document**: Exact differences and required changes +- **Verify**: Template variables handle all variations + +### 1. Incremental Template Updates (File-by-File Approach) + +#### Phase 1: Add `__init__.py` + +- **Add**: Empty `__init__.py` file to template +- **Validate**: `make p-gen` → `make p-validate` → `make lint` + +#### Phase 2: Fix `solution.py` + +- **Update**: Modern syntax (`TreeNode | None`), clean template logic +- **Validate**: `make p-gen` → `make p-validate` → `make lint` + +#### Phase 3: Fix `tests.py` + +- **Update**: Relative imports (`from .solution`), clean structure +- **Validate**: `make p-gen` → `make p-validate` → `make lint` + +#### Phase 4: Fix `README.md` + +- **Update**: Clean formatting, proper markdown +- **Validate**: `make p-gen` → `make p-validate` → `make lint` + +#### Phase 5: Fix `playground.ipynb` + +- **Update**: Clean cells without execution state +- **Validate**: `make p-gen` → `make p-validate` → `make lint` + +**Benefits**: Isolated debugging, safer progression, easier rollback + +### 2. Multi-Problem Type Testing + +- **Test Cases**: Ensure template handles all problem types + - Basic array: `two_sum` (return `list[int]`) + - Tree: `invert_binary_tree` (return `TreeNode | None`) + - String: problems returning `str` + - Boolean: problems returning `bool` + - No imports vs TreeNode/ListNode imports +- **Validation**: Each type generates correctly + +### 3. Modernize cookiecutter.json Schema + +- **Base on**: Existing `.templates/leetcode/.example/examples/` JSON5 files +- **Ensure**: All template variables are properly defined +- **Add**: Missing fields found in real examples +- **Note**: Variable mapping handled by `gen.py` `convert_arrays_to_nested()` + +### 4. Update Template Files + +#### solution.py + +- Use modern type hints (`TreeNode | None` not `Optional[TreeNode]`) +- Match exact import patterns from real examples +- Ensure proper TODO placeholder format + +#### tests.py + +- Follow `@logged_test` decorator pattern +- Use parametrized pytest structure +- Match logging format from real examples + +#### README.md + +- Follow exact format from real examples +- Include proper problem description formatting + +#### playground.ipynb + +- Ensure notebook structure matches real examples + +### 5. Template Generation Logic + +- **File**: `.templates/leetcode/gen.py` (already handles variable mapping) +- **Integration**: Works with `make p-gen PROBLEM=name` (verified in Makefile) +- **Update**: Handle new `__init__.py` file +- **Process**: JSON → `gen.py` → cookiecutter → `leetcode/$(PROBLEM)/` + +### 6. Automated Validation System + +- **Tool**: Reusable `.amazonq/plan/compare_template_files.py` +- **Usage**: + ```bash + # Validate current template generates correct files + poetry run python .amazonq/plan/compare_template_files.py generated --problem=invert_binary_tree + ``` +- **Makefile**: `make p-validate PROBLEM=name` (implemented) +- **Test**: Template regression testing +- **Ensure**: `make p-gen` + `make lint` + `make p-test` all pass + +### 7. Testing & Validation + +- **Test**: Template generation with existing JSON files +- **Verify**: Generated files match `leetcode/.example/` structure exactly +- **Compare**: Automated diff against reference files +- **Ensure**: `make p-gen` works seamlessly +- **Test**: Recreation process from `.prompt/` files +- **Validate**: Multi-problem type generation + +## Key Template Variables to Ensure + +```json +{ + "problem_name": "snake_case_name", + "class_name": "PascalCaseName", + "method_name": "snake_case_method", + "problem_number": "226", + "problem_title": "Display Title", + "difficulty": "Easy|Medium|Hard", + "topics": "Comma, Separated, Topics", + "tags": ["grind-75", "blind-75"], + "problem_description": "Full description", + "examples": [{"input": "...", "output": "..."}], + "constraints": "Formatted constraints", + "parameters": "typed_params: list[int]", + "return_type": "TreeNode | None", + "imports": "from leetcode_py.tree_node import TreeNode", + "test_cases": [{"args": [...], "expected": ...}] +} +``` + +## Success Criteria + +### Phase-by-Phase Validation (File-by-File) + +1. ✅ **Phase 1**: `__init__.py` files generated correctly +2. ✅ **Phase 2**: `solution.py` with modern syntax (`TreeNode | None`) +3. ✅ **Phase 3**: `tests.py` with relative imports and clean structure +4. ✅ **Phase 4**: `README.md` with clean formatting +5. ✅ **Phase 5**: `playground.ipynb` with clean cells + +### Multi-Problem Type Validation + +5. ✅ Basic problems (array/string) generate correctly +6. ✅ Tree problems generate correctly +7. ✅ Different return types handled (`bool`, `int`, `str`, `list`, etc.) + +### Automated Validation + +8. ✅ Automated diff shows no differences vs `leetcode/.example/` +9. ✅ `make p-validate` passes for all problem types +10. ✅ Recreation from `.prompt/` works flawlessly +11. ✅ All linting passes (`make lint`) +12. ✅ Tests run successfully (`make p-test`) + +## Files to Modify + +### Template Files + +1. `.templates/leetcode/{{cookiecutter.problem_name}}/` + - **Add**: `__init__.py` (empty file) + - **Update**: `solution.py` (modern syntax, imports) + - **Update**: `tests.py` (match `leetcode/.example/` format) + - **Update**: `README.md` (match `leetcode/.example/` format) + - **Update**: `playground.ipynb` (match structure) + +### Configuration + +2. `.templates/leetcode/cookiecutter.json` + - Align with JSON5 examples + - Add missing variables + +### Generation Logic + +3. `.templates/leetcode/gen.py` + - Handle `__init__.py` generation + - Maintain existing variable mapping + +### Validation Tools + +4. **Reusable**: `.amazonq/plan/compare_template_files.py` (handles both template and generated comparisons) +5. **New**: Makefile target `make p-validate` + +## Risk Mitigation + +- **Incremental phases** prevent all-or-nothing failures +- **Automated validation** catches regressions immediately +- **Multi-problem testing** ensures template generalization +- **Explicit file comparison** documents exact requirements + +This plan ensures the template generates files that exactly match `leetcode/.example/` while maintaining the robust generation process described in the rules. diff --git a/.amazonq/rules/development-rules.md b/.amazonq/rules/development-rules.md index 0bd6531..671ffc8 100644 --- a/.amazonq/rules/development-rules.md +++ b/.amazonq/rules/development-rules.md @@ -2,53 +2,27 @@ ## Discussion Mode -- **Discussion Mode**: Prefix prompt with "D:" to enter read-only discussion mode -- In discussion mode: NO code updates, only read files and provide analysis/suggestions -- Always start responses with "[Discussion Mode]" header when in discussion mode -- Never exit discussion mode automatically - only when user uses "XD:" prefix -- If user seems to want code changes, remind them to use "XD:" to exit discussion mode -- **Exit Discussion**: Use "XD:" prefix to exit discussion mode and resume normal operations +- **Enter**: Prefix with "D:" for read-only analysis mode +- **Exit**: Use "XD:" to resume normal operations +- In discussion mode: NO code updates, only analysis/suggestions ## Code Standards -- Use snake_case for Python method names (following Python convention) -- Always include type hints for function parameters and return types -- Use PEP 585/604 syntax: `list[str]`, `dict[str, int]`, `Type | None`, etc. -- Add return statements to satisfy type checkers even if unreachable -- Follow the project's linting rules (black, isort, ruff, mypy) - -## Template Usage - -- **When user copies LeetCode problem**: Use `leetcode/_template/` to structure the question -- Copy template files to new question directory: `leetcode/{question_name}/` -- Replace template placeholders with actual problem details: - - `{method_name}` - snake_case method name (e.g., `two_sum`) - - `{ClassName}` - PascalCase class name (e.g., `TwoSum`) - - `{parameters}` - method parameters with types - - `{return_type}` - return type annotation - - Test case placeholders with actual examples -- **Template Implementation**: Do NOT implement the Solution class - only provide test cases and structure -- **Helper Functions/Classes**: If the question relies on underlying helper functions or classes (e.g., TreeNode, ListNode): - - First check if implementation already exists in `leetcode_py/common/` directory - - If found, import from common module - - If not found, create shared implementation in `leetcode_py/common/` for reusable classes - - For question-specific helpers, implement directly in the solution file -- **Update Makefile**: When adding new question, update the default `QUESTION` value in Makefile to the new question name -- Always use the template structure for consistency +- Use snake_case for Python methods +- Include type hints: `list[str]`, `dict[str, int]`, `Type | None` +- Follow linting rules (black, isort, ruff, mypy) -## File Structure +## Testing -Each LeetCode problem should have: +- Test specific: `make p-test PROBLEM=` +- Test all: `make test` +- Beautiful logging with loguru -- `README.md` - Problem description and examples -- `solution.py` - Solution implementation -- `tests.py` - Parametrized pytest tests with loguru logging -- `__init__.py` - Empty file for Python package +## File Structure -## Testing +Each problem has: -- Use `make test-question QUESTION=` to run tests -- Use `make test` to run all questions with coverage -- Default question is set to `two_sum` in Makefile -- Tests should cover all provided examples -- Use loguru for beautiful logging in tests +- `README.md` - Problem description +- `solution.py` - Implementation with TODO placeholder +- `tests.py` - Parametrized pytest tests +- `__init__.py` - Empty package file diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md new file mode 100644 index 0000000..42e3793 --- /dev/null +++ b/.amazonq/rules/problem-creation.md @@ -0,0 +1,31 @@ +# Problem Creation Guide + +## Quick Steps + +1. Create JSON: `.templates/leetcode/json/{problem_name}.json` +2. Update Makefile: `PROBLEM ?= your_new_problem` +3. Generate: `make p-gen` +4. Verify: `make lint` +5. **If you edit generated files**: Update JSON template, then `make p-gen FORCE=1` to ensure reproducibility + +## JSON Template Rules + +- **Copy from reference examples** - don't create from scratch +- **Tree problems**: Use `.templates/leetcode/examples/tree.json5` +- **Basic problems**: Use `.templates/leetcode/examples/basic.json5` +- **Don't add extra fields** - templates are complete +- **Python naming convention**: Use snake_case for all parameter names (e.g., `new_interval` not `newInterval`) +- **If lint fails**: Fix JSON and regenerate, don't edit generated files +- **After any manual edits**: Always update JSON template and verify with `make p-gen FORCE=1` + +## Tags (Optional) + +```json +"tags": ["grind-75", "blind-75", "neetcode-150", "top-interview"] +``` + +## Helper Classes + +- TreeNode: `from leetcode_py.tree_node import TreeNode` +- ListNode: `from leetcode_py.list_node import ListNode` +- New helpers: Add to `leetcode_py/` diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index a5dc7b1..ff9b686 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -36,6 +36,17 @@ jobs: if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-ansi + - name: Cache Graphviz + id: cache-graphviz + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: /usr/bin/dot + key: graphviz-${{ runner.os }} + + - name: Install Graphviz + if: steps.cache-graphviz.outputs.cache-hit != 'true' + run: sudo apt-get update && sudo apt-get install -y graphviz + - name: Run tests run: make test diff --git a/.gitignore b/.gitignore index 484c224..84cc5e8 100644 --- a/.gitignore +++ b/.gitignore @@ -529,11 +529,11 @@ terragrunt-debug.tfvars.json ### VisualStudioCode ### .vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets +# !.vscode/settings.json +# !.vscode/tasks.json +# !.vscode/launch.json +# !.vscode/extensions.json +# !.vscode/*.code-snippets # Local History for Visual Studio Code .history/ diff --git a/.templates/leetcode/cookiecutter.json b/.templates/leetcode/cookiecutter.json new file mode 100644 index 0000000..e44999d --- /dev/null +++ b/.templates/leetcode/cookiecutter.json @@ -0,0 +1,41 @@ +{ + "problem_name": "two_sum", + "class_name": "TwoSum", + "method_name": "two_sum", + "problem_number": "1", + "problem_title": "Two Sum", + "difficulty": "Easy", + "topics": "Array, Hash Table", + "_tags": { "list": ["grind-75"] }, + "problem_description": "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.", + "_examples": { + "list": [ + { "input": "nums = [2,7,11,15], target = 9", "output": "[0,1]" }, + { "input": "nums = [3,2,4], target = 6", "output": "[1,2]" }, + { "input": "nums = [3,3], target = 6", "output": "[0,1]" } + ] + }, + "constraints": "- 2 <= nums.length <= 10^4\n- -10^9 <= nums[i] <= 10^9\n- -10^9 <= target <= 10^9\n- Only one valid answer exists.", + "parameters": "nums: list[int], target: int", + "return_type": "list[int]", + "dummy_return": "[]", + "imports": "", + "test_setup": "", + "test_logging": "", + "_test_cases": { + "list": [ + { "args": [[2, 7, 11, 15], 9], "expected": [0, 1] }, + { "args": [[3, 2, 4], 6], "expected": [1, 2] }, + { "args": [[3, 3], 6], "expected": [0, 1] } + ] + }, + "param_names": "nums, target, expected", + "param_names_with_types": "nums: list[int], target: int, expected: list[int]", + "input_description": "nums={nums}, target={target}", + "input_params": "nums, target", + "expected_param": "expected", + "method_args": "nums, target", + "test_input_setup": "nums = [2, 7, 11, 15]\ntarget = 9", + "expected_output_setup": "expected = [0, 1]", + "assertion_code": "assert result == expected" +} diff --git a/.templates/leetcode/examples/README.md b/.templates/leetcode/examples/README.md new file mode 100644 index 0000000..0f59d7c --- /dev/null +++ b/.templates/leetcode/examples/README.md @@ -0,0 +1,71 @@ +# LeetCode Template Examples + +Reference templates for creating new LeetCode problems. **Copy from these examples** - don't create from scratch. + +## Usage + +1. **Choose the right template** based on problem type +2. **Copy the entire structure** to `.templates/leetcode/json/{problem_name}.json` +3. **Update all fields** with your problem's data +4. **Generate**: `make p-gen PROBLEM=your_problem` + +## Templates + +### `basic.json5` + +- **Use for**: Array, string, number, hash table problems +- **Examples**: Two Sum, Valid Anagram, Contains Duplicate +- **Features**: Simple parameters, direct assertions + +### `tree.json5` + +- **Use for**: Binary tree problems +- **Examples**: Invert Binary Tree, Maximum Depth, Same Tree +- **Features**: TreeNode import, array-to-tree conversion, tree logging + +### `linked_list.json5` + +- **Use for**: Linked list problems +- **Examples**: Reverse Linked List, Merge Two Lists, Cycle Detection +- **Features**: ListNode import, array-to-list conversion, list logging + +### `string.json5` + +- **Use for**: String manipulation problems +- **Examples**: Valid Palindrome, Longest Substring, Anagrams +- **Features**: String parameters, boolean/string returns + +### `matrix.json5` + +- **Use for**: 2D array/matrix problems +- **Examples**: Rotate Image, Spiral Matrix, Set Matrix Zeroes +- **Features**: Matrix parameters, in-place operation testing + +## Key Fields + +### Required Core Fields + +- `problem_name`, `class_name`, `method_name` +- `problem_number`, `problem_title`, `difficulty`, `topics` +- `problem_description`, `examples`, `constraints` +- `parameters`, `return_type`, `dummy_return` + +### Test Configuration + +- `test_cases`: Array of `{args, expected}` objects +- `param_names`: Parameter names for test methods +- `test_setup`: Code to convert test data (e.g., arrays to TreeNode) +- `assertion_code`: How to compare result with expected + +### Notebook Setup + +- `test_input_setup`: Code for notebook input cell +- `expected_output_setup`: Code for notebook expected cell +- `imports`: Required imports (TreeNode, ListNode, etc.) + +## Rules + +1. **Copy structure exactly** - all fields are required +2. **Use modern Python syntax**: `list[int]`, `TreeNode | None` +3. **Match existing patterns** - see current JSON files for reference +4. **Test thoroughly** - run `make lint` and `make p-test` after generation diff --git a/.templates/leetcode/examples/basic.json5 b/.templates/leetcode/examples/basic.json5 new file mode 100644 index 0000000..8f0e1ba --- /dev/null +++ b/.templates/leetcode/examples/basic.json5 @@ -0,0 +1,61 @@ +{ + // Basic problem template for array/string/number problems + // Copy this structure when creating new basic problems + + // REQUIRED: Core identifiers + "problem_name": "two_sum", + "class_name": "TwoSum", + "method_name": "two_sum", + + // REQUIRED: Problem metadata + "problem_number": "1", + "problem_title": "Two Sum", + "difficulty": "Easy", + "topics": "Array, Hash Table", + + // OPTIONAL: Problem categorization + "tags": ["grind-75"], + + // REQUIRED: Problem description + "problem_description": "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.", + + // REQUIRED: Examples + "examples": [ + { "input": "nums = [2,7,11,15], target = 9", "output": "[0,1]" }, + { "input": "nums = [3,2,4], target = 6", "output": "[1,2]" }, + { "input": "nums = [3,3], target = 6", "output": "[0,1]" } + ], + + // REQUIRED: Constraints + "constraints": "- 2 <= nums.length <= 10^4\n- -10^9 <= nums[i] <= 10^9\n- -10^9 <= target <= 10^9\n- Only one valid answer exists.", + + // REQUIRED: Method signature + "parameters": "nums: list[int], target: int", + "return_type": "list[int]", + "dummy_return": "[]", + + // REQUIRED: Imports (empty for basic problems) + "imports": "", + + // REQUIRED: Test cases + "test_cases": [ + { "args": [[2, 7, 11, 15], 9], "expected": [0, 1] }, + { "args": [[3, 2, 4], 6], "expected": [1, 2] }, + { "args": [[3, 3], 6], "expected": [0, 1] } + ], + + // REQUIRED: Test configuration + "param_names": "nums, target, expected", + "param_names_with_types": "nums: list[int], target: int, expected: list[int]", + "input_description": "nums={nums}, target={target}", + "input_params": "nums, target", + "expected_param": "expected", + "method_args": "nums, target", + "test_setup": "", + "test_logging": "", + "assertion_code": "assert result == expected", + + // REQUIRED: Notebook setup + "test_input_setup": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9", + "expected_output_setup": "expected = [0, 1]" +} diff --git a/.templates/leetcode/examples/linked_list.json5 b/.templates/leetcode/examples/linked_list.json5 new file mode 100644 index 0000000..06d0b13 --- /dev/null +++ b/.templates/leetcode/examples/linked_list.json5 @@ -0,0 +1,62 @@ +{ + // Linked list problem template + // Use this for problems involving ListNode structures + + // REQUIRED: Core identifiers + "problem_name": "reverse_linked_list_ii", + "class_name": "ReverseLinkedListII", + "method_name": "reverse_between", + + // REQUIRED: Problem metadata + "problem_number": "92", + "problem_title": "Reverse Linked List II", + "difficulty": "Medium", + "topics": "Linked List", + + // OPTIONAL: Problem categorization + "tags": [], + + // REQUIRED: Problem description + "problem_description": "Given the head of a singly linked list and two integers left and right where left <= right, reverse the nodes of the list from position left to position right, and return the reversed list.", + + // REQUIRED: Examples + "examples": [ + { "input": "head = [1,2,3,4,5], left = 2, right = 4", "output": "[1,4,3,2,5]" }, + { "input": "head = [5], left = 1, right = 1", "output": "[5]" } + ], + + // REQUIRED: Constraints + "constraints": "- The number of nodes in the list is n.\n- 1 <= n <= 500\n- -500 <= Node.val <= 500\n- 1 <= left <= right <= n", + + // REQUIRED: Method signature + "parameters": "head: ListNode | None, left: int, right: int", + "return_type": "ListNode | None", + "dummy_return": "None", + + // REQUIRED: ListNode import for linked list problems + "imports": "from leetcode_py.list_node import ListNode", + + // REQUIRED: Test cases + "test_cases": [ + { "args": [[1, 2, 3, 4, 5], 2, 4], "expected": [1, 4, 3, 2, 5] }, + { "args": [[5], 1, 1], "expected": [5] }, + { "args": [[1, 2, 3], 1, 3], "expected": [3, 2, 1] } + ], + + // REQUIRED: Test parameters (use expected_list for linked list problems) + "param_names": "head_list, left, right, expected_list", + "param_names_with_types": "head_list: list[int], left: int, right: int, expected_list: list[int]", + "input_description": "head_list={head_list}, left={left}, right={right}", + "input_params": "head, left, right", + "expected_param": "expected", + "method_args": "head, left, right", + + // REQUIRED: Linked list-specific test setup + "test_setup": "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)", + "test_logging": "logger.success(f\"Got result: {result.to_list() if result else []}\")", + "assertion_code": "assert result == expected", + + // REQUIRED: Notebook setup for linked list problems + "test_input_setup": "# Example test case\nhead = ListNode.from_list([1, 2, 3, 4, 5])\nleft = 2\nright = 4", + "expected_output_setup": "expected = ListNode.from_list([1, 4, 3, 2, 5])" +} diff --git a/.templates/leetcode/examples/matrix.json5 b/.templates/leetcode/examples/matrix.json5 new file mode 100644 index 0000000..e0cb669 --- /dev/null +++ b/.templates/leetcode/examples/matrix.json5 @@ -0,0 +1,59 @@ +{ + // Matrix problem template + // Use this for 2D array/matrix problems + + // REQUIRED: Core identifiers + "problem_name": "rotate_image", + "class_name": "RotateImage", + "method_name": "rotate", + + // REQUIRED: Problem metadata + "problem_number": "48", + "problem_title": "Rotate Image", + "difficulty": "Medium", + "topics": "Array, Math, Matrix", + + // OPTIONAL: Problem categorization + "tags": ["grind-75"], + + // REQUIRED: Problem description + "problem_description": "You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).\n\nYou have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.", + + // REQUIRED: Examples + "examples": [ + { "input": "matrix = [[1,2,3],[4,5,6],[7,8,9]]", "output": "[[7,4,1],[8,5,2],[9,6,3]]" }, + { "input": "matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]", "output": "[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]" } + ], + + // REQUIRED: Constraints + "constraints": "- n == matrix.length == matrix[i].length\n- 1 <= n <= 20\n- -1000 <= matrix[i][j] <= 1000", + + // REQUIRED: Method signature + "parameters": "matrix: list[list[int]]", + "return_type": "None", + "dummy_return": "None", + + // REQUIRED: Imports (empty for matrix problems) + "imports": "", + + // REQUIRED: Test cases (for in-place operations, test the modified matrix) + "test_cases": [ + { "args": [[[1,2,3],[4,5,6],[7,8,9]]], "expected": [[7,4,1],[8,5,2],[9,6,3]] }, + { "args": [[[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]], "expected": [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] } + ], + + // REQUIRED: Test configuration + "param_names": "matrix, expected", + "param_names_with_types": "matrix: list[list[int]], expected: list[list[int]]", + "input_description": "matrix={matrix}", + "input_params": "matrix", + "expected_param": "expected", + "method_args": "matrix", + "test_setup": "", + "test_logging": "", + "assertion_code": "assert matrix == expected", + + // REQUIRED: Notebook setup + "test_input_setup": "# Example test case\nmatrix = [[1,2,3],[4,5,6],[7,8,9]]", + "expected_output_setup": "expected = [[7,4,1],[8,5,2],[9,6,3]]" +} diff --git a/.templates/leetcode/examples/string.json5 b/.templates/leetcode/examples/string.json5 new file mode 100644 index 0000000..61411a7 --- /dev/null +++ b/.templates/leetcode/examples/string.json5 @@ -0,0 +1,61 @@ +{ + // String problem template + // Use this for string manipulation problems + + // REQUIRED: Core identifiers + "problem_name": "valid_palindrome", + "class_name": "ValidPalindrome", + "method_name": "is_palindrome", + + // REQUIRED: Problem metadata + "problem_number": "125", + "problem_title": "Valid Palindrome", + "difficulty": "Easy", + "topics": "Two Pointers, String", + + // OPTIONAL: Problem categorization + "tags": ["grind-75"], + + // REQUIRED: Problem description + "problem_description": "A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.\n\nGiven a string s, return true if it is a palindrome, or false otherwise.", + + // REQUIRED: Examples + "examples": [ + { "input": "s = \"A man, a plan, a canal: Panama\"", "output": "true" }, + { "input": "s = \"race a car\"", "output": "false" }, + { "input": "s = \" \"", "output": "true" } + ], + + // REQUIRED: Constraints + "constraints": "- 1 <= s.length <= 2 * 10^5\n- s consists only of printable ASCII characters.", + + // REQUIRED: Method signature + "parameters": "s: str", + "return_type": "bool", + "dummy_return": "False", + + // REQUIRED: Imports (empty for string problems) + "imports": "", + + // REQUIRED: Test cases + "test_cases": [ + { "args": ["A man, a plan, a canal: Panama"], "expected": true }, + { "args": ["race a car"], "expected": false }, + { "args": [" "], "expected": true } + ], + + // REQUIRED: Test configuration + "param_names": "s, expected", + "param_names_with_types": "s: str, expected: bool", + "input_description": "s=\"{s}\"", + "input_params": "s", + "expected_param": "expected", + "method_args": "s", + "test_setup": "", + "test_logging": "", + "assertion_code": "assert result == expected", + + // REQUIRED: Notebook setup + "test_input_setup": "# Example test case\ns = \"A man, a plan, a canal: Panama\"", + "expected_output_setup": "expected = True" +} diff --git a/.templates/leetcode/examples/tree.json5 b/.templates/leetcode/examples/tree.json5 new file mode 100644 index 0000000..ae66571 --- /dev/null +++ b/.templates/leetcode/examples/tree.json5 @@ -0,0 +1,63 @@ +{ + // Tree problem template for binary tree problems + // Use this for problems involving TreeNode structures + + // REQUIRED: Core identifiers + "problem_name": "invert_binary_tree", + "class_name": "InvertBinaryTree", + "method_name": "invert_tree", + + // REQUIRED: Problem metadata + "problem_number": "226", + "problem_title": "Invert Binary Tree", + "difficulty": "Easy", + "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", + + // OPTIONAL: Problem categorization + "tags": ["grind-75"], + + // REQUIRED: Problem description + "problem_description": "Given the root of a binary tree, invert the tree, and return its root.", + + // REQUIRED: Examples (tree problems show array representation) + "examples": [ + { "input": "root = [4,2,7,1,3,6,9]", "output": "[4,7,2,9,6,3,1]" }, + { "input": "root = [2,1,3]", "output": "[2,3,1]" }, + { "input": "root = []", "output": "[]" } + ], + + // REQUIRED: Constraints + "constraints": "- The number of nodes in the tree is in the range [0, 100].\n- -100 <= Node.val <= 100", + + // REQUIRED: Method signature (TreeNode | None for nullable tree parameters) + "parameters": "root: TreeNode | None", + "return_type": "TreeNode | None", + "dummy_return": "None", + + // REQUIRED: TreeNode import for tree problems + "imports": "from leetcode_py.tree_node import TreeNode", + + // REQUIRED: Test cases (use array representation for tree inputs/outputs) + "test_cases": [ + { "args": [[4, 2, 7, 1, 3, 6, 9]], "expected": [4, 7, 2, 9, 6, 3, 1] }, + { "args": [[2, 1, 3]], "expected": [2, 3, 1] }, + { "args": [[]], "expected": [] } + ], + + // REQUIRED: Test parameters (use expected_list for tree problems) + "param_names": "root_list, expected_list", + "param_names_with_types": "root_list: list[int | None], expected_list: list[int | None]", + "input_description": "root_list={root_list}", + "input_params": "root", + "expected_param": "expected", + "method_args": "root", + + // REQUIRED: Tree-specific test setup (converts arrays to TreeNode objects) + "test_setup": "root = TreeNode.from_list(root_list)\nexpected = TreeNode.from_list(expected_list)", + "test_logging": "logger.success(f\"Got result: {result.to_list() if result else []}\")", + "assertion_code": "assert result == expected", + + // REQUIRED: Notebook setup for tree problems + "test_input_setup": "# Example test case\nroot = TreeNode.from_list([4, 2, 7, 1, 3, 6, 9])", + "expected_output_setup": "expected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])" +} diff --git a/.templates/leetcode/gen.py b/.templates/leetcode/gen.py new file mode 100644 index 0000000..4de54a7 --- /dev/null +++ b/.templates/leetcode/gen.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Generate LeetCode problem from JSON using cookiecutter.""" + +import json +from pathlib import Path + +import typer +from cookiecutter.main import cookiecutter +import sys + + +def check_and_prompt_tags(data: dict) -> dict: + import sys + + common_tags = ["grind-75", "blind-75", "neetcode-150", "top-interview"] + + if "tags" in data and (not data["tags"] or data["tags"] == []): + if sys.stdin.isatty(): # Interactive terminal + typer.echo("\n📋 No tags specified. Would you like to add any common tags?") + typer.echo("Available options:") + for i, tag in enumerate(common_tags, 1): + typer.echo(f" {i}. {tag}") + typer.echo(" 0. Skip (no tags)") + + choices_input = typer.prompt("Select options (comma-separated, e.g. '1,2' or '0' to skip)") + + try: + choices = [int(x.strip()) for x in choices_input.split(",")] + selected_tags = [] + + for choice in choices: + if choice == 0: + selected_tags = [] + break + elif 1 <= choice <= len(common_tags): + tag = common_tags[choice - 1] + if tag not in selected_tags: + selected_tags.append(tag) + + data["tags"] = selected_tags + if selected_tags: + typer.echo(f"✅ Added tags: {', '.join(selected_tags)}") + else: + typer.echo("✅ No tags added") + + except ValueError: + typer.echo("⚠️ Invalid input, skipping tags") + data["tags"] = [] + + return data + + +def auto_set_dummy_return(data: dict) -> dict: + if "dummy_return" not in data and "return_type" in data: + return_type = data["return_type"] + dummy_map = {"bool": "False", "int": "0", "str": '""', "float": "0.0", "None": "None"} + + if return_type in dummy_map: + data["dummy_return"] = dummy_map[return_type] + elif return_type.startswith("list["): + data["dummy_return"] = "[]" + elif return_type.startswith("dict["): + data["dummy_return"] = "{}" + elif return_type.startswith("set["): + data["dummy_return"] = "set()" + elif return_type.startswith("tuple["): + data["dummy_return"] = "()" + else: + data["dummy_return"] = "None" + + return data + + +def convert_arrays_to_nested(data: dict) -> dict: + extra_context = data.copy() + array_fields = ["examples", "test_cases", "tags"] + for field in array_fields: + if field in data and isinstance(data[field], list): + extra_context[f"_{field}"] = {"list": data[field]} + del extra_context[field] + return extra_context + + +def check_overwrite_permission(problem_name: str, force: bool) -> None: + + if force: + return + + output_dir = Path(__file__).parent.parent.parent / "leetcode" + problem_dir = output_dir / problem_name + + if not problem_dir.exists(): + return + + typer.echo(f"⚠️ Warning: Problem '{problem_name}' already exists in leetcode/", err=True) + typer.echo("This will overwrite existing files. Use --force to skip this check.", err=True) + + if sys.stdin.isatty(): # Interactive terminal + confirm = typer.confirm("Continue?") + if not confirm: + typer.echo("Cancelled.") + raise typer.Exit(1) + else: # Non-interactive mode + typer.echo("Non-interactive mode: use --force to overwrite.", err=True) + raise typer.Exit(1) + + +def generate_problem(json_file: str, force: bool = False) -> None: + json_path = Path(json_file) + if not json_path.exists(): + typer.echo(f"Error: {json_file} not found", err=True) + raise typer.Exit(1) + + # Load JSON data + with open(json_path) as f: + data = json.load(f) + + # Check and prompt for tags if empty + data = check_and_prompt_tags(data) + + # Auto-set dummy_return if not provided + data = auto_set_dummy_return(data) + + # Save updated data back to JSON file + with open(json_path, "w") as f: + json.dump(data, f) + + # Convert arrays to cookiecutter-friendly nested format + extra_context = convert_arrays_to_nested(data) + + # Check if problem already exists + problem_name = extra_context.get("problem_name", "unknown") + check_overwrite_permission(problem_name, force) + + # Generate project using cookiecutter + template_dir = Path(__file__).parent + output_dir = template_dir.parent.parent / "leetcode" + + cookiecutter( + str(template_dir), + extra_context=extra_context, + no_input=True, + overwrite_if_exists=True, + output_dir=str(output_dir), + ) + + typer.echo(f"✅ Generated problem: {problem_name}") + + +if __name__ == "__main__": + typer.run(generate_problem) diff --git a/.templates/leetcode/json/insert_interval.json b/.templates/leetcode/json/insert_interval.json new file mode 100644 index 0000000..935cfd2 --- /dev/null +++ b/.templates/leetcode/json/insert_interval.json @@ -0,0 +1,68 @@ +{ + "problem_name": "insert_interval", + "class_name": "InsertInterval", + "method_name": "insert", + "problem_number": "57", + "problem_title": "Insert Interval", + "difficulty": "Medium", + "topics": "Array", + "tags": ["grind-75"], + "problem_description": "You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval new_interval = [start, end] that represents the start and end of another interval.\n\nInsert new_interval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary).\n\nReturn intervals after the insertion.", + "examples": [ + { "input": "intervals = [[1,3],[6,9]], new_interval = [2,5]", "output": "[[1,5],[6,9]]" }, + { + "input": "intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], new_interval = [4,8]", + "output": "[[1,2],[3,10],[12,16]]" + } + ], + "constraints": "- 0 <= intervals.length <= 10^4\n- intervals[i].length == 2\n- 0 <= starti <= endi <= 10^5\n- intervals is sorted by starti in ascending order.\n- new_interval.length == 2\n- 0 <= start <= end <= 10^5", + "parameters": "intervals: list[list[int]], new_interval: list[int]", + "return_type": "list[list[int]]", + "dummy_return": "[]", + "imports": "", + "test_cases": [ + { + "args": [ + [ + [1, 3], + [6, 9] + ], + [2, 5] + ], + "expected": [ + [1, 5], + [6, 9] + ] + }, + { + "args": [ + [ + [1, 2], + [3, 5], + [6, 7], + [8, 10], + [12, 16] + ], + [4, 8] + ], + "expected": [ + [1, 2], + [3, 10], + [12, 16] + ] + }, + { "args": [[], [5, 7]], "expected": [[5, 7]] }, + { "args": [[[1, 5]], [2, 3]], "expected": [[1, 5]] } + ], + "param_names": "intervals, new_interval, expected", + "param_names_with_types": "intervals: list[list[int]], new_interval: list[int], expected: list[list[int]]", + "input_description": "intervals={intervals}, new_interval={new_interval}", + "input_params": "intervals, new_interval", + "expected_param": "expected", + "method_args": "intervals, new_interval", + "test_setup": "", + "test_logging": "", + "assertion_code": "assert result == expected", + "test_input_setup": "# Example test case\nintervals = [[1,3],[6,9]]\nnew_interval = [2,5]", + "expected_output_setup": "expected = [[1,5],[6,9]]" +} diff --git a/.templates/leetcode/json/invert_binary_tree.json b/.templates/leetcode/json/invert_binary_tree.json new file mode 100644 index 0000000..c0a07c5 --- /dev/null +++ b/.templates/leetcode/json/invert_binary_tree.json @@ -0,0 +1,37 @@ +{ + "problem_name": "invert_binary_tree", + "class_name": "InvertBinaryTree", + "method_name": "invert_tree", + "problem_number": "226", + "problem_title": "Invert Binary Tree", + "difficulty": "Easy", + "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", + "tags": ["grind-75"], + "problem_description": "Given the root of a binary tree, invert the tree, and return its root.", + "examples": [ + { "input": "root = [4,2,7,1,3,6,9]", "output": "[4,7,2,9,6,3,1]" }, + { "input": "root = [2,1,3]", "output": "[2,3,1]" }, + { "input": "root = []", "output": "[]" } + ], + "constraints": "- The number of nodes in the tree is in the range [0, 100].\n- -100 <= Node.val <= 100", + "parameters": "root: TreeNode | None", + "return_type": "TreeNode | None", + "dummy_return": "None", + "imports": "from leetcode_py.tree_node import TreeNode", + "test_cases": [ + { "args": [[4, 2, 7, 1, 3, 6, 9]], "expected": [4, 7, 2, 9, 6, 3, 1] }, + { "args": [[2, 1, 3]], "expected": [2, 3, 1] }, + { "args": [[]], "expected": [] } + ], + "param_names": "root_list, expected_list", + "param_names_with_types": "root_list: list[int | None], expected_list: list[int | None]", + "input_description": "root_list={root_list}", + "input_params": "root", + "expected_param": "expected", + "method_args": "root", + "test_setup": "root = TreeNode.from_list(root_list)\nexpected = TreeNode.from_list(expected_list)", + "test_logging": "logger.success(f\"Got result: {result.to_list() if result else []}\")", + "assertion_code": "assert result == expected", + "test_input_setup": "# Example test case\nroot = TreeNode.from_list([4, 2, 7, 1, 3, 6, 9])", + "expected_output_setup": "expected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])" +} diff --git a/.templates/leetcode/json/reverse_linked_list_ii.json b/.templates/leetcode/json/reverse_linked_list_ii.json new file mode 100644 index 0000000..73e2d6e --- /dev/null +++ b/.templates/leetcode/json/reverse_linked_list_ii.json @@ -0,0 +1,35 @@ +{ + "assertion_code": "assert result == expected", + "class_name": "ReverseLinkedListII", + "constraints": "- The number of nodes in the list is n.\n- 1 <= n <= 500\n- -500 <= Node.val <= 500\n- 1 <= left <= right <= n", + "difficulty": "Medium", + "examples": [ + { "input": "head = [1,2,3,4,5], left = 2, right = 4", "output": "[1,4,3,2,5]" }, + { "input": "head = [5], left = 1, right = 1", "output": "[5]" } + ], + "expected_output_setup": "expected = ListNode.from_list([1, 4, 3, 2, 5])", + "expected_param": "expected", + "imports": "from leetcode_py.list_node import ListNode", + "input_description": "head_list={head_list}, left={left}, right={right}", + "input_params": "head, left, right", + "method_args": "head, left, right", + "method_name": "reverse_between", + "param_names": "head_list, left, right, expected_list", + "param_names_with_types": "head_list: list[int], left: int, right: int, expected_list: list[int]", + "parameters": "head: ListNode | None, left: int, right: int", + "problem_description": "Given the head of a singly linked list and two integers left and right where left <= right, reverse the nodes of the list from position left to position right, and return the reversed list.", + "problem_number": "92", + "problem_title": "Reverse Linked List II", + "problem_name": "reverse_linked_list_ii", + "return_type": "ListNode | None", + "dummy_return": "None", + "test_cases": [ + { "args": [[1, 2, 3, 4, 5], 2, 4], "expected": [1, 4, 3, 2, 5] }, + { "args": [[5], 1, 1], "expected": [5] }, + { "args": [[1, 2, 3], 1, 3], "expected": [3, 2, 1] } + ], + "test_input_setup": "# Example test case\nhead = ListNode.from_list([1, 2, 3, 4, 5])\nleft = 2\nright = 4", + "test_logging": "logger.success(f\"Got result: {result.to_list() if result else []}\")", + "test_setup": "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)", + "topics": "Linked List" +} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/README.md b/.templates/leetcode/{{cookiecutter.problem_name}}/README.md new file mode 100644 index 0000000..b281830 --- /dev/null +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/README.md @@ -0,0 +1,29 @@ +# {{cookiecutter.problem_number}}. {{cookiecutter.problem_title}} + +**Difficulty:** {{cookiecutter.difficulty}} +**Topics:** {{cookiecutter.topics}} +**Tags:** {% for _, tags in cookiecutter._tags | dictsort %}{{ tags | join(', ') }}{% endfor %} +**LeetCode:** [Problem {{cookiecutter.problem_number}}](https://leetcode.com/problems/{{cookiecutter.problem_name.replace('_', "-")}}/description/) + +## Problem Description + +{{cookiecutter.problem_description}} + +## Examples + +{%- for _, examples in cookiecutter._examples | dictsort %} +{%- for example in examples %} + +### Example {{ loop.index }}: + +``` +Input: {{ example.input }} +Output: {{ example.output }} +``` + +{%- endfor %} +{%- endfor %} + +## Constraints + +{{cookiecutter.constraints}} diff --git a/leetcode/two_sum/__init__.py b/.templates/leetcode/{{cookiecutter.problem_name}}/__init__.py similarity index 100% rename from leetcode/two_sum/__init__.py rename to .templates/leetcode/{{cookiecutter.problem_name}}/__init__.py diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb new file mode 100644 index 0000000..cf7f6af --- /dev/null +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution"{%- if cookiecutter.imports and cookiecutter.imports.strip() %}, + "", + "{{cookiecutter.imports}}"{%- endif %} + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + {% for line in cookiecutter.test_input_setup.split('\n') %}"{{line}}"{%- if not loop.last %}, + {% endif %}{% endfor %}, + "{{cookiecutter.expected_output_setup}}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "execute", + "metadata": {}, + "outputs": [], + "source": [ + "result = Solution().{{cookiecutter.method_name}}({{cookiecutter.method_args}})", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "{{cookiecutter.assertion_code}}" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py new file mode 100644 index 0000000..92302e5 --- /dev/null +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py @@ -0,0 +1,9 @@ +{{cookiecutter.imports}} + + +class Solution: + # Time: O(?) + # Space: O(?) + def {{cookiecutter.method_name}}(self, {{cookiecutter.parameters}}) -> {{cookiecutter.return_type}}: + # TODO: Implement solution + return {{cookiecutter.dummy_return}} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py b/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py new file mode 100644 index 0000000..22a9024 --- /dev/null +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py @@ -0,0 +1,42 @@ +import pytest +from loguru import logger + +{{cookiecutter.imports}} +from leetcode_py.test_utils import logged_test + +from .solution import Solution + + +class Test{{cookiecutter.class_name}}: + def setup_method(self): + self.solution = Solution() + + @pytest.mark.parametrize( + "{{cookiecutter.param_names}}", + [ + {%- for _, test_cases in cookiecutter._test_cases | dictsort %} + {%- for test_case in test_cases %} + ({% for arg in test_case.args %}{% if arg is string %}"{{ arg }}"{% else %}{{ arg }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}, {{ test_case.expected }}), + {%- endfor %} + {%- endfor %} + ], + ) + @logged_test + def test_{{cookiecutter.method_name}}(self, {{cookiecutter.param_names_with_types}}): + logger.info(f"Testing with {{cookiecutter.input_description}}") + {%- if cookiecutter.test_setup %} + {%- for line in cookiecutter.test_setup.split('\n') %} + {{line}} + {%- endfor %} + {%- endif %} + result = self.solution.{{cookiecutter.method_name}}({{cookiecutter.input_params}}) + {%- if cookiecutter.test_logging %} + {{cookiecutter.test_logging}} + {%- else %} + logger.success(f"Got result: {result}") + {%- endif %} + {%- if cookiecutter.assertion_code %} + {{cookiecutter.assertion_code}} + {%- else %} + assert result == {{cookiecutter.expected_param}} + {%- endif %} diff --git a/Makefile b/Makefile index 21e29b9..a3a1721 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ PYTHON_VERSION = 3.13 -QUESTION ?= invert_binary_tree +PROBLEM ?= insert_interval +FORCE ?= 0 sync_submodules: git submodule update --init --recursive --remote @@ -20,29 +21,64 @@ assert_setup_dev: lint: poetry sort + npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}" poetry run black . poetry run isort . - poetry run ruff check . + poetry run nbqa ruff . --nbqa-exclude=".templates" --ignore=F401,F821 + poetry run ruff check . --exclude="**/*.ipynb" poetry run mypy \ --explicit-package-bases \ --install-types \ --non-interactive \ --check-untyped-defs . - npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}" + poetry run nbqa isort . --nbqa-exclude=".templates" + poetry run nbqa mypy . \ + --nbqa-exclude=".templates" \ + --ignore-missing-imports \ + --disable-error-code=name-defined test: - poetry run pytest leetcode/ \ + poetry run pytest leetcode/ tests/ \ -v --cov=leetcode --cov=leetcode_py \ --cov-report=term-missing \ --cov-report=xml \ - --ignore=leetcode/_template \ + --ignore=.templates \ --ignore=leetcode/__pycache__ -test-question: - @echo "Testing question: $(QUESTION)" - @if [ ! -d "leetcode/$(QUESTION)" ]; then \ - echo "Error: Question '$(QUESTION)' not found in leetcode/ directory"; \ +# Test Problems +p-test: + @echo "Testing problem: $(PROBLEM)" + @if [ ! -d "leetcode/$(PROBLEM)" ]; then \ + echo "Error: Problem '$(PROBLEM)' not found in leetcode/ directory"; \ + exit 1; \ + fi + poetry run pytest leetcode/$(PROBLEM)/tests.py -v -s + +# Generate Problem +p-gen: + @echo "Generating problem: $(PROBLEM)" + poetry run python .templates/leetcode/gen.py .templates/leetcode/json/$(PROBLEM).json $(if $(filter 1,$(FORCE)),--force) + +# Generate All Problems - useful for people who fork this repo +gen-all-problems: + @echo "This will DELETE all existing problems and regenerate from JSON templates." + @read -p "Are you sure? (y/N): " confirm && [ "$$confirm" = "y" ] || exit 1 + @echo "Deleting existing problems..." + @rm -rf leetcode/*/ + @echo "Generating all problems..." + @for json_file in .templates/leetcode/json/*.json; do \ + problem=$$(basename "$$json_file" .json); \ + echo "Generating: $$problem"; \ + poetry run python .templates/leetcode/gen.py "$$json_file" $(if $(filter 1,$(FORCE)),--force); \ + done + +# Validate Problem - INTERNAL USE ONLY: For cookiecutter template creation/validation +# Do not use during normal problem solving - only for template development +p-validate: + @echo "Validating problem: $(PROBLEM)" + @if [ ! -d "leetcode/$(PROBLEM)" ]; then \ + echo "Error: Generated problem '$(PROBLEM)' not found. Run: make p-gen PROBLEM=$(PROBLEM)"; \ exit 1; \ fi - poetry run pytest leetcode/$(QUESTION)/tests.py -v -s + poetry run python .amazonq/plan/compare_template_files.py generated --problem=$(PROBLEM) diff --git a/README.md b/README.md index 65419e8..00249b4 100644 --- a/README.md +++ b/README.md @@ -7,53 +7,156 @@ [![tests](https://img.shields.io/github/actions/workflow/status/wisarootl/leetcode-py/ci-test.yml?branch=main&label=tests&logo=github)](https://github.com/wisarootl/zerv/actions/workflows/ci-test.yml) [![release](https://img.shields.io/github/actions/workflow/status/wisarootl/leetcode-py/cd.yml?branch=main&label=release&logo=github)](https://github.com/wisarootl/zerv/actions/workflows/cd.yml) -Premium LeetCode practice environment with modern Python tooling, beautiful tree visualizations, and comprehensive testing. +Premium LeetCode practice repository with Python solutions, algorithm templates, data structure visualizations, and automated testing. Perfect for coding interview preparation, competitive programming, and mastering algorithms with Blind 75, Grind 75, and NeetCode 150 problems. + +## 📋 Prerequisites + +- Python 3.9+ +- make +- git +- Optional: Graphviz for tree visualizations + +## 🛠️ Installation + +```bash +# Clone the repository +git clone https://github.com/wisarootl/leetcode-py.git +cd leetcode-py + +# Install dependencies +pip install -r requirements.txt + +# Generate all problems +make gen-all-problems + +# Verify setup +make test +``` + +## 📁 Problem Structure + +Each problem follows a consistent template: + +``` +leetcode/two_sum/ +├── README.md # Problem description and examples +├── solution.py # Your implementation with TODO placeholder +├── tests.py # Comprehensive test cases +├── notebook.ipynb # Interactive playground +└── __init__.py # Package marker +``` + +## 🎯 Supported Problem Categories + +- **Arrays & Hashing** - Two Sum, Group Anagrams, Top K Elements +- **Two Pointers** - Valid Palindrome, Container With Most Water +- **Sliding Window** - Longest Substring, Minimum Window +- **Stack** - Valid Parentheses, Daily Temperatures +- **Binary Search** - Search Rotated Array, Find Minimum +- **Linked Lists** - Reverse List, Merge Lists, Detect Cycle +- **Trees** - Invert Tree, Maximum Depth, Serialize/Deserialize +- **Tries** - Implement Trie, Word Search II +- **Heap/Priority Queue** - Merge K Lists, Find Median +- **Backtracking** - Combination Sum, Word Search, N-Queens +- **Graphs** - Clone Graph, Course Schedule, Islands +- **Advanced DP** - Climbing Stairs, Coin Change, LCS +- **Greedy** - Jump Game, Gas Station +- **Intervals** - Merge Intervals, Meeting Rooms +- **Math & Geometry** - Rotate Image, Spiral Matrix + +Includes problems from **Blind 75**, **Grind 75**, **NeetCode 150**, and **Top Interview Questions**. This is an ongoing project - contributions are welcome! + +## 🎨 Visualizations + +### Tree Visualization + +![Tree Visualization Placeholder](docs/images/tree-viz.png) +_Beautiful tree rendering with anytree and Graphviz_ + +### Linked List Visualization + +![LinkedList Visualization Placeholder](docs/images/linkedlist-viz.png) +_Clean arrow-based list visualization_ + +### Jupyter Notebook Integration + +![Notebook Placeholder](docs/images/notebook-example.png) +_Interactive multi-cell playground for each problem_ ## ✨ Features - **Template-driven development** - Consistent structure for every problem -- **Beautiful tree visualizations** - Pretty-printed binary trees with anytree -- **Rich test logging** - `@logged_test` decorator with detailed tracebacks -- **One-command testing** - `make test-question QUESTION=problem_name` -- **Code quality** - black, isort, ruff, mypy integration +- **Beautiful visualizations** - TreeNode with anytree/Graphviz, ListNode with arrows +- **Interactive notebooks** - Multi-cell playground for each problem +- **One-command testing** - `make p-test PROBLEM=problem_name` +- **Bulk regeneration** - `make gen-all-problems` from JSON templates +- **Full linting** - black, isort, ruff, mypy with nbqa for notebooks - **Modern Python** - PEP 585/604 syntax with full type hints ## 🚀 Quick Start ```bash +# Generate all problems to start practicing +make gen-all-problems + # Run existing problems -make test-question QUESTION=two_sum -make test-question QUESTION=invert_binary_tree +make p-test PROBLEM=insert_interval +make p-test PROBLEM=invert_binary_tree # Run all tests make test ``` -**Adding new problems**: Use an LLM agent (rules in `.amazonq/rules/development-rules.md`) to automatically create new problems from copied LeetCode problem text using the template structure. +## 🔄 Workflow Examples -## 🧰 Commands +**Practice existing problems**: ```bash -make test-question QUESTION=two_sum # Test specific problem -make test # Run all tests -make lint # Code quality checks +# Work on a specific problem +make p-test PROBLEM=two_sum +# Edit leetcode/two_sum/solution.py +# Run tests to verify ``` -## 🎨 Example Output +**Add new problems**: +```bash +# Copy problem description and solution placeholder from LeetCode +# Then ask your LLM assistant: +# "Create a new LeetCode problem for Valid Anagram" +# +# Behind the scenes, the LLM will: +# 1. Create JSON template following .amazonq/rules/problem-creation.md +# 2. Run `make p-gen PROBLEM=valid_anagram` +# 3. Generate complete problem structure with tests +# 4. You just implement the solution! ``` -# TreeNode visualization -4 -├── 2 -│ ├── 1 -│ └── 3 -└── 7 - ├── 6 - └── 9 - -# Test logging -2024-01-01 10:00:00 | SUCCESS | Got result: [4,7,2,9,6,3,1] -2024-01-01 10:00:00 | DEBUG | Test passed! ✨ + +_The LLM follows structured rules in `.amazonq/rules/problem-creation.md` to ensure consistent, high-quality problem generation using proven templates._ + +**Bulk operations**: + +```bash +# Test all problems +make test +# Regenerate all from templates +make gen-all-problems +# Check code quality +make lint ``` +## 🧰 Helper Classes + +- **TreeNode**: `from leetcode_py.tree_node import TreeNode` + - Beautiful tree visualization with anytree rendering + - Jupyter notebook support with Graphviz diagrams + - Easy array ↔ tree conversion for testing +- **ListNode**: `from leetcode_py.list_node import ListNode` + - Clean arrow visualization (`1 -> 2 -> 3`) + - Simple array ↔ list conversion + - Perfect for debugging linked list problems +- New helpers: Add to `leetcode_py/` + +This is an ongoing project - contributions are welcome! + Perfect for interview preparation with professional-grade tooling and beautiful visualizations. diff --git a/docs/images/linkedlist-viz.png b/docs/images/linkedlist-viz.png new file mode 100644 index 0000000..46df3b5 Binary files /dev/null and b/docs/images/linkedlist-viz.png differ diff --git a/docs/images/notebook-example.png b/docs/images/notebook-example.png new file mode 100644 index 0000000..64d848e Binary files /dev/null and b/docs/images/notebook-example.png differ diff --git a/docs/images/tree-viz.png b/docs/images/tree-viz.png new file mode 100644 index 0000000..adca1b1 Binary files /dev/null and b/docs/images/tree-viz.png differ diff --git a/leetcode/_template/README.md b/leetcode/_template/README.md deleted file mode 100644 index 2931b15..0000000 --- a/leetcode/_template/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# {PROBLEM_NUMBER}. {PROBLEM_TITLE} - -**Difficulty:** {DIFFICULTY} -**Topics:** {TOPICS} -**Tags:** {TAGS} - -## Problem Description - -{PROBLEM_DESCRIPTION} - -## Examples - -### Example 1: - -``` -Input: {INPUT_1} -Output: {OUTPUT_1} -Explanation: {EXPLANATION_1} -``` - -### Example 2: - -``` -Input: {INPUT_2} -Output: {OUTPUT_2} -``` - -### Example 3: - -``` -Input: {INPUT_3} -Output: {OUTPUT_3} -``` - -## Constraints - -{CONSTRAINTS} - -## Follow-up - -{FOLLOW_UP} - -**Time Complexity:** O(?) -**Space Complexity:** O(?) diff --git a/leetcode/_template/solution.py b/leetcode/_template/solution.py deleted file mode 100644 index 58a2463..0000000 --- a/leetcode/_template/solution.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import List - - -class Solution: - # Time: O(?) - # Space: O(?) - def {method_name}(self, {parameters}) -> {return_type}: - # TODO: Implement solution - pass diff --git a/leetcode/_template/tests.py b/leetcode/_template/tests.py deleted file mode 100644 index e7d9ad0..0000000 --- a/leetcode/_template/tests.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest -from loguru import logger -from leetcode_py.test_utils import logged_test -from .solution import Solution - - -class Test{ClassName}: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "{param_names}", - [ - ({test_case_1}), - ({test_case_2}), - ({test_case_3}), - ], - ) - @logged_test - def test_{method_name}(self, {param_names}): - logger.info(f"Testing with {input_description}") - result = self.solution.{method_name}({input_params}) - logger.success(f"Got result: {result}") - assert result == {expected_param} diff --git a/leetcode/insert_interval/README.md b/leetcode/insert_interval/README.md new file mode 100644 index 0000000..40384cf --- /dev/null +++ b/leetcode/insert_interval/README.md @@ -0,0 +1,39 @@ +# 57. Insert Interval + +**Difficulty:** Medium +**Topics:** Array +**Tags:** grind-75 +**LeetCode:** [Problem 57](https://leetcode.com/problems/insert-interval/description/) + +## Problem Description + +You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval new_interval = [start, end] that represents the start and end of another interval. + +Insert new_interval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary). + +Return intervals after the insertion. + +## Examples + +### Example 1: + +``` +Input: intervals = [[1,3],[6,9]], new_interval = [2,5] +Output: [[1,5],[6,9]] +``` + +### Example 2: + +``` +Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], new_interval = [4,8] +Output: [[1,2],[3,10],[12,16]] +``` + +## Constraints + +- 0 <= intervals.length <= 10^4 +- intervals[i].length == 2 +- 0 <= starti <= endi <= 10^5 +- intervals is sorted by starti in ascending order. +- new_interval.length == 2 +- 0 <= start <= end <= 10^5 diff --git a/leetcode/insert_interval/__init__.py b/leetcode/insert_interval/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/insert_interval/playground.ipynb b/leetcode/insert_interval/playground.ipynb new file mode 100644 index 0000000..f9d36a7 --- /dev/null +++ b/leetcode/insert_interval/playground.ipynb @@ -0,0 +1,80 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "intervals = [[1, 3], [6, 9]]\n", + "new_interval = [2, 5]\n", + "expected = [[1, 5], [6, 9]]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[1, 5], [6, 9]]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = Solution().insert(intervals, new_interval)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "assert result == expected" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/insert_interval/solution.py b/leetcode/insert_interval/solution.py new file mode 100644 index 0000000..25ce116 --- /dev/null +++ b/leetcode/insert_interval/solution.py @@ -0,0 +1,22 @@ +class Solution: + # Time: O(n) + # Space: O(n) + def insert(self, intervals: list[list[int]], new_interval: list[int]) -> list[list[int]]: + result = [] + i = 0 + + # Add intervals before new_interval + while i < len(intervals) and intervals[i][1] < new_interval[0]: + result.append(intervals[i]) + i += 1 + + # Merge overlapping intervals + while i < len(intervals) and intervals[i][0] <= new_interval[1]: + new_interval[0] = min(new_interval[0], intervals[i][0]) + new_interval[1] = max(new_interval[1], intervals[i][1]) + i += 1 + result.append(new_interval) + + # Add remaining intervals + result.extend(intervals[i:]) + return result diff --git a/leetcode/insert_interval/tests.py b/leetcode/insert_interval/tests.py new file mode 100644 index 0000000..7025338 --- /dev/null +++ b/leetcode/insert_interval/tests.py @@ -0,0 +1,29 @@ +import pytest +from loguru import logger + +from leetcode_py.test_utils import logged_test + +from .solution import Solution + + +class TestInsertInterval: + def setup_method(self): + self.solution = Solution() + + @pytest.mark.parametrize( + "intervals, new_interval, expected", + [ + ([[1, 3], [6, 9]], [2, 5], [[1, 5], [6, 9]]), + ([[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], [4, 8], [[1, 2], [3, 10], [12, 16]]), + ([], [5, 7], [[5, 7]]), + ([[1, 5]], [2, 3], [[1, 5]]), + ], + ) + @logged_test + def test_insert( + self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]] + ): + logger.info(f"Testing with intervals={intervals}, new_interval={new_interval}") + result = self.solution.insert(intervals, new_interval) + logger.success(f"Got result: {result}") + assert result == expected diff --git a/leetcode/invert_binary_tree/README.md b/leetcode/invert_binary_tree/README.md index 0266142..0cc21ed 100644 --- a/leetcode/invert_binary_tree/README.md +++ b/leetcode/invert_binary_tree/README.md @@ -3,6 +3,7 @@ **Difficulty:** Easy **Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree **Tags:** grind-75 +**LeetCode:** [Problem 226](https://leetcode.com/problems/invert-binary-tree/description/) ## Problem Description @@ -35,6 +36,3 @@ Output: [] - The number of nodes in the tree is in the range [0, 100]. - -100 <= Node.val <= 100 - -**Time Complexity:** O(?) -**Space Complexity:** O(?) diff --git a/leetcode/invert_binary_tree/playground.ipynb b/leetcode/invert_binary_tree/playground.ipynb new file mode 100644 index 0000000..3ba0c7d --- /dev/null +++ b/leetcode/invert_binary_tree/playground.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution\n", + "\n", + "from leetcode_py.tree_node import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root = TreeNode.from_list([4, 2, 7, 1, 3, 6, 9])\n", + "expected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "0->4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "1->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "5\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "4->5\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "6\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "4->6\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeNode([4, 7, 2, 9, 6, 3, 1])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = Solution().invert_tree(root)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "assert result == expected" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/invert_binary_tree/solution.py b/leetcode/invert_binary_tree/solution.py index 59774f5..5df0b83 100644 --- a/leetcode/invert_binary_tree/solution.py +++ b/leetcode/invert_binary_tree/solution.py @@ -2,11 +2,11 @@ class Solution: - # Time: O(n) - visit each node once - # Space: O(h) - recursion stack depth equals tree height + # Time: O(n) + # Space: O(h) def invert_tree(self, root: TreeNode | None) -> TreeNode | None: if not root: - return root + return None root.left, root.right = self.invert_tree(root.right), self.invert_tree(root.left) return root diff --git a/leetcode/invert_binary_tree/tests.py b/leetcode/invert_binary_tree/tests.py index 5d97c55..4d2ba0a 100644 --- a/leetcode/invert_binary_tree/tests.py +++ b/leetcode/invert_binary_tree/tests.py @@ -12,29 +12,18 @@ def setup_method(self): self.solution = Solution() @pytest.mark.parametrize( - "input_arr, expected_arr", + "root_list, expected_list", [ ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], []), - ([1], [1]), - ([1, 2], [1, None, 2]), - ([1, None, 2], [1, 2]), - ([1, 2, 3, 4, 5], [1, 3, 2, None, None, 5, 4]), - ([3, 9, 20, None, None, 15, 7], [3, 20, 9, 7, 15]), ], ) @logged_test - def test_invert_tree(self, input_arr: list[int | None], expected_arr: list[int | None]): - logger.info(f"Testing with input: {input_arr}") - root = TreeNode.from_list(input_arr) - if root: - logger.debug(f"Input tree:\n{root}") - + def test_invert_tree(self, root_list: list[int | None], expected_list: list[int | None]): + logger.info(f"Testing with root_list={root_list}") + root = TreeNode.from_list(root_list) + expected = TreeNode.from_list(expected_list) result = self.solution.invert_tree(root) - result_arr = result.to_list() if result else [] - - if result: - logger.debug(f"Result tree:\n{result}") - logger.success(f"Got result: {result_arr}") - assert result_arr == expected_arr + logger.success(f"Got result: {result.to_list() if result else []}") + assert result == expected diff --git a/leetcode/reverse_linked_list_ii/README.md b/leetcode/reverse_linked_list_ii/README.md new file mode 100644 index 0000000..2a9c21a --- /dev/null +++ b/leetcode/reverse_linked_list_ii/README.md @@ -0,0 +1,33 @@ +# 92. Reverse Linked List II + +**Difficulty:** Medium +**Topics:** Linked List +**Tags:** grind-75 +**LeetCode:** [Problem 92](https://leetcode.com/problems/reverse-linked-list-ii/description/) + +## Problem Description + +Given the head of a singly linked list and two integers left and right where left <= right, reverse the nodes of the list from position left to position right, and return the reversed list. + +## Examples + +### Example 1: + +``` +Input: head = [1,2,3,4,5], left = 2, right = 4 +Output: [1,4,3,2,5] +``` + +### Example 2: + +``` +Input: head = [5], left = 1, right = 1 +Output: [5] +``` + +## Constraints + +- The number of nodes in the list is n. +- 1 <= n <= 500 +- -500 <= Node.val <= 500 +- 1 <= left <= right <= n diff --git a/leetcode/reverse_linked_list_ii/__init__.py b/leetcode/reverse_linked_list_ii/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/reverse_linked_list_ii/playground.ipynb b/leetcode/reverse_linked_list_ii/playground.ipynb new file mode 100644 index 0000000..74f09dc --- /dev/null +++ b/leetcode/reverse_linked_list_ii/playground.ipynb @@ -0,0 +1,86 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution\n", + "\n", + "from leetcode_py.list_node import ListNode" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "head = ListNode.from_list([1, 2, 3, 4, 5])\n", + "left = 2\n", + "right = 4\n", + "expected = ListNode.from_list([1, 4, 3, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "1 -> 4 -> 3 -> 2 -> 5" + ], + "text/plain": [ + "ListNode([1, 4, 3, 2, 5])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = Solution().reverse_between(head, left, right)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "assert result == expected" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/reverse_linked_list_ii/solution.py b/leetcode/reverse_linked_list_ii/solution.py new file mode 100644 index 0000000..2b7e88b --- /dev/null +++ b/leetcode/reverse_linked_list_ii/solution.py @@ -0,0 +1,30 @@ +from leetcode_py.list_node import ListNode + + +class Solution: + # Time: O(n) + # Space: O(1) + def reverse_between(self, head: ListNode | None, left: int, right: int) -> ListNode | None: + if not head or left == right: + return head + + dummy = ListNode(0) + dummy.next = head + prev = dummy + + # Move to position before left + for _ in range(left - 1): + assert prev.next + prev = prev.next + + # Reverse from left to right + assert prev.next + curr = prev.next + for _ in range(right - left): + assert curr.next + next_node = curr.next + curr.next = next_node.next + next_node.next = prev.next + prev.next = next_node + + return dummy.next diff --git a/leetcode/reverse_linked_list_ii/tests.py b/leetcode/reverse_linked_list_ii/tests.py new file mode 100644 index 0000000..23eb3b7 --- /dev/null +++ b/leetcode/reverse_linked_list_ii/tests.py @@ -0,0 +1,31 @@ +import pytest +from loguru import logger + +from leetcode_py.list_node import ListNode +from leetcode_py.test_utils import logged_test + +from .solution import Solution + + +class TestReverseLinkedListII: + def setup_method(self): + self.solution = Solution() + + @pytest.mark.parametrize( + "head_list, left, right, expected_list", + [ + ([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), + ([5], 1, 1, [5]), + ([1, 2, 3], 1, 3, [3, 2, 1]), + ], + ) + @logged_test + def test_reverse_between( + self, head_list: list[int], left: int, right: int, expected_list: list[int] + ): + logger.info(f"Testing with head_list={head_list}, left={left}, right={right}") + head = ListNode.from_list(head_list) + expected = ListNode.from_list(expected_list) + result = self.solution.reverse_between(head, left, right) + logger.success(f"Got result: {result.to_list() if result else []}") + assert result == expected diff --git a/leetcode/two_sum/README.md b/leetcode/two_sum/README.md deleted file mode 100644 index bcca541..0000000 --- a/leetcode/two_sum/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Two Sum - -**Difficulty:** Easy -**Topics:** Array, Hash Table -**Tags:** grind-75 - -## Problem Description - -Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to target. - -You may assume that each input would have exactly one solution, and you may not use the same element twice. - -You can return the answer in any order. - -## Examples - -### Example 1: - -``` -Input: nums = [2,7,11,15], target = 9 -Output: [0,1] -Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. -``` - -### Example 2: - -``` -Input: nums = [3,2,4], target = 6 -Output: [1,2] -``` - -### Example 3: - -``` -Input: nums = [3,3], target = 6 -Output: [0,1] -``` - -## Constraints - -- `2 <= nums.length <= 10^4` -- `-10^9 <= nums[i] <= 10^9` -- `-10^9 <= target <= 10^9` -- Only one valid answer exists. - -## Follow-up - -Can you come up with an algorithm that is less than O(n²) time complexity? - -**Time Complexity:** O(n) -**Space Complexity:** O(n) diff --git a/leetcode/two_sum/solution.py b/leetcode/two_sum/solution.py deleted file mode 100644 index 3d691a3..0000000 --- a/leetcode/two_sum/solution.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import List - - -class Solution: - # Time: O(n) - single pass through array - # Space: O(n) - hash map stores up to n elements - def two_sum(self, nums: List[int], target: int) -> List[int]: - seen: dict[int, int] = {} - for i, num in enumerate(nums): - remaining = target - num - if remaining in seen: - return [seen[remaining], i] - seen[num] = i - return [] diff --git a/leetcode/two_sum/tests.py b/leetcode/two_sum/tests.py deleted file mode 100644 index d7629fa..0000000 --- a/leetcode/two_sum/tests.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest -from loguru import logger - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestTwoSum: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, target, expected", - [ - ([2, 7, 11, 15], 9, [0, 1]), - ([3, 2, 4], 6, [1, 2]), - ([3, 3], 6, [0, 1]), - ], - ) - @logged_test - def test_two_sum(self, nums, target, expected): - logger.info(f"Testing with nums: {nums}, target: {target}") - result = self.solution.two_sum(nums, target) - logger.success(f"Got result: {result}") - assert result == expected diff --git a/leetcode_py/list_node.py b/leetcode_py/list_node.py new file mode 100644 index 0000000..6759952 --- /dev/null +++ b/leetcode_py/list_node.py @@ -0,0 +1,37 @@ +class ListNode: + def __init__(self, val: int = 0, next: "ListNode | None" = None): + self.val = val + self.next = next + + @classmethod + def from_list(cls, arr: list[int]) -> "ListNode | None": + if not arr: + return None + head = cls(arr[0]) + current = head + for val in arr[1:]: + current.next = cls(val) + current = current.next + return head + + def to_list(self) -> list[int]: + result = [] + current: "ListNode | None" = self + while current: + result.append(current.val) + current = current.next + return result + + def __str__(self) -> str: + return " -> ".join(str(val) for val in self.to_list()) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.to_list()})" + + def _repr_html_(self) -> str: + return self.__str__() + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ListNode): + return False + return self.to_list() == other.to_list() diff --git a/leetcode_py/test_utils.py b/leetcode_py/test_utils.py index ebf6cda..2eba88d 100644 --- a/leetcode_py/test_utils.py +++ b/leetcode_py/test_utils.py @@ -2,13 +2,17 @@ from loguru import logger +# # Configure logger to disable backtrace +# logger.remove() +# logger.add(sys.stderr, backtrace=False) + def logged_test(func): """Decorator to add consistent logging to test methods.""" @wraps(func) def wrapper(*args, **kwargs): - logger.info("") + print("") try: result = func(*args, **kwargs) logger.debug("Test passed! ✨") diff --git a/leetcode_py/tree_node.py b/leetcode_py/tree_node.py index 2e7cc61..bef27f7 100644 --- a/leetcode_py/tree_node.py +++ b/leetcode_py/tree_node.py @@ -1,3 +1,4 @@ +import graphviz from anytree import Node, RenderTree @@ -72,5 +73,35 @@ def __str__(self) -> str: return str(None) return "\n".join([f"{pre}{node.name}" for pre, _, node in RenderTree(tree)]) + def _repr_html_(self) -> str: + dot = graphviz.Digraph() + dot.attr(rankdir="TB") + + def add_nodes(node: "TreeNode | None", node_id: int = 0) -> int: + if not node: + return node_id + + dot.node(str(node_id), str(node.val)) + current_id = node_id + next_id = node_id + 1 + + if node.left: + dot.edge(str(current_id), str(next_id)) + next_id = add_nodes(node.left, next_id) + 1 + + if node.right: + dot.edge(str(current_id), str(next_id)) + next_id = add_nodes(node.right, next_id) + 1 + + return next_id - 1 + + add_nodes(self) + return dot.pipe(format="svg", encoding="utf-8") + + def __eq__(self, other: object) -> bool: + if not isinstance(other, TreeNode): + return False + return self.to_list() == other.to_list() + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.to_list()})" diff --git a/poetry.lock b/poetry.lock index 2533ec0..df16983 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,12 +6,91 @@ version = "2.13.0" description = "Powerful and Lightweight Python Tree Data Structure with various plugins" optional = false python-versions = "<4.0,>=3.9.2" -groups = ["dev"] +groups = ["base"] files = [ {file = "anytree-2.13.0-py3-none-any.whl", hash = "sha256:4cbcf10df36b1f1cba131b7e487ff3edafc9d6e932a3c70071b5b768bab901ff"}, {file = "anytree-2.13.0.tar.gz", hash = "sha256:c9d3aa6825fdd06af7ebb05b4ef291d2db63e62bb1f9b7d9b71354be9d362714"}, ] +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +markers = "platform_system == \"Darwin\"" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "arrow" +version = "1.3.0" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] + +[[package]] +name = "asttokens" +version = "3.0.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, +] + +[package.extras] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "autopep8" +version = "2.3.2" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, + {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, +] + +[package.dependencies] +pycodestyle = ">=2.12.0" + +[[package]] +name = "binaryornot" +version = "0.4.4" +description = "Ultra-lightweight pure Python package to check if a file is binary or text." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, + {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, +] + +[package.dependencies] +chardet = ">=3.0.2" + [[package]] name = "black" version = "25.1.0" @@ -57,13 +136,219 @@ d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + [[package]] name = "click" version = "8.2.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, @@ -78,12 +363,49 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +groups = ["main", "base", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", base = "sys_platform == \"win32\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} + +[[package]] +name = "comm" +version = "0.2.3" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417"}, + {file = "comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971"}, +] + +[package.extras] +test = ["pytest"] + +[[package]] +name = "cookiecutter" +version = "2.6.0" +description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d"}, + {file = "cookiecutter-2.6.0.tar.gz", hash = "sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c"}, +] + +[package.dependencies] +arrow = "*" +binaryornot = ">=0.4.4" +click = ">=7.0,<9.0.0" +Jinja2 = ">=2.7,<4.0.0" +python-slugify = ">=4.0.0" +pyyaml = ">=5.3.1" +requests = ">=2.23.0" +rich = "*" [[package]] name = "coverage" @@ -187,228 +509,835 @@ files = [ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] -name = "iniconfig" -version = "2.1.0" -description = "brain-dead simple config-ini parsing" +name = "debugpy" +version = "1.8.16" +description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "debugpy-1.8.16-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2a3958fb9c2f40ed8ea48a0d34895b461de57a1f9862e7478716c35d76f56c65"}, + {file = "debugpy-1.8.16-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ca7314042e8a614cc2574cd71f6ccd7e13a9708ce3c6d8436959eae56f2378"}, + {file = "debugpy-1.8.16-cp310-cp310-win32.whl", hash = "sha256:8624a6111dc312ed8c363347a0b59c5acc6210d897e41a7c069de3c53235c9a6"}, + {file = "debugpy-1.8.16-cp310-cp310-win_amd64.whl", hash = "sha256:fee6db83ea5c978baf042440cfe29695e1a5d48a30147abf4c3be87513609817"}, + {file = "debugpy-1.8.16-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67371b28b79a6a12bcc027d94a06158f2fde223e35b5c4e0783b6f9d3b39274a"}, + {file = "debugpy-1.8.16-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2abae6dd02523bec2dee16bd6b0781cccb53fd4995e5c71cc659b5f45581898"}, + {file = "debugpy-1.8.16-cp311-cp311-win32.whl", hash = "sha256:f8340a3ac2ed4f5da59e064aa92e39edd52729a88fbde7bbaa54e08249a04493"}, + {file = "debugpy-1.8.16-cp311-cp311-win_amd64.whl", hash = "sha256:70f5fcd6d4d0c150a878d2aa37391c52de788c3dc680b97bdb5e529cb80df87a"}, + {file = "debugpy-1.8.16-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:b202e2843e32e80b3b584bcebfe0e65e0392920dc70df11b2bfe1afcb7a085e4"}, + {file = "debugpy-1.8.16-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64473c4a306ba11a99fe0bb14622ba4fbd943eb004847d9b69b107bde45aa9ea"}, + {file = "debugpy-1.8.16-cp312-cp312-win32.whl", hash = "sha256:833a61ed446426e38b0dd8be3e9d45ae285d424f5bf6cd5b2b559c8f12305508"}, + {file = "debugpy-1.8.16-cp312-cp312-win_amd64.whl", hash = "sha256:75f204684581e9ef3dc2f67687c3c8c183fde2d6675ab131d94084baf8084121"}, + {file = "debugpy-1.8.16-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:85df3adb1de5258dca910ae0bb185e48c98801ec15018a263a92bb06be1c8787"}, + {file = "debugpy-1.8.16-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee89e948bc236a5c43c4214ac62d28b29388453f5fd328d739035e205365f0b"}, + {file = "debugpy-1.8.16-cp313-cp313-win32.whl", hash = "sha256:cf358066650439847ec5ff3dae1da98b5461ea5da0173d93d5e10f477c94609a"}, + {file = "debugpy-1.8.16-cp313-cp313-win_amd64.whl", hash = "sha256:b5aea1083f6f50023e8509399d7dc6535a351cc9f2e8827d1e093175e4d9fa4c"}, + {file = "debugpy-1.8.16-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:2801329c38f77c47976d341d18040a9ac09d0c71bf2c8b484ad27c74f83dc36f"}, + {file = "debugpy-1.8.16-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687c7ab47948697c03b8f81424aa6dc3f923e6ebab1294732df1ca9773cc67bc"}, + {file = "debugpy-1.8.16-cp38-cp38-win32.whl", hash = "sha256:a2ba6fc5d7c4bc84bcae6c5f8edf5988146e55ae654b1bb36fecee9e5e77e9e2"}, + {file = "debugpy-1.8.16-cp38-cp38-win_amd64.whl", hash = "sha256:d58c48d8dbbbf48a3a3a638714a2d16de537b0dace1e3432b8e92c57d43707f8"}, + {file = "debugpy-1.8.16-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:135ccd2b1161bade72a7a099c9208811c137a150839e970aeaf121c2467debe8"}, + {file = "debugpy-1.8.16-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:211238306331a9089e253fd997213bc4a4c65f949271057d6695953254095376"}, + {file = "debugpy-1.8.16-cp39-cp39-win32.whl", hash = "sha256:88eb9ffdfb59bf63835d146c183d6dba1f722b3ae2a5f4b9fc03e925b3358922"}, + {file = "debugpy-1.8.16-cp39-cp39-win_amd64.whl", hash = "sha256:c2c47c2e52b40449552843b913786499efcc3dbc21d6c49287d939cd0dbc49fd"}, + {file = "debugpy-1.8.16-py2.py3-none-any.whl", hash = "sha256:19c9521962475b87da6f673514f7fd610328757ec993bf7ec0d8c96f9a325f9e"}, + {file = "debugpy-1.8.16.tar.gz", hash = "sha256:31e69a1feb1cf6b51efbed3f6c9b0ef03bc46ff050679c4be7ea6d2e23540870"}, ] [[package]] -name = "isort" -version = "6.0.1" -description = "A Python utility / library to sort Python imports." +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" optional = false -python-versions = ">=3.9.0" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, - {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, ] -[package.extras] -colors = ["colorama"] -plugins = ["setuptools"] - [[package]] -name = "loguru" -version = "0.7.3" -description = "Python logging made (stupidly) simple" +name = "distlib" +version = "0.4.0" +description = "Distribution utilities" optional = false -python-versions = "<4.0,>=3.5" +python-versions = "*" groups = ["dev"] files = [ - {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, - {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] -[package.dependencies] -colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} -win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} - -[package.extras] -dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] - [[package]] -name = "mypy" -version = "1.17.1" -description = "Optional static typing for Python" +name = "executing" +version = "2.2.0" +description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, - {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, - {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, - {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, - {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, - {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, - {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, - {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, - {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, - {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, - {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, - {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, - {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, - {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, - {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, - {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, + {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, + {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] -[package.dependencies] -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -typing_extensions = ">=4.6.0" - [package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] [[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." +name = "filelock" +version = "3.19.1" +description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] [[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" +name = "graphviz" +version = "0.21" +description = "Simple Python interface for Graphviz" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, + {file = "graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42"}, + {file = "graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78"}, ] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] +[package.extras] +dev = ["Flake8-pyproject", "build", "flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] +docs = ["sphinx (>=5,<7)", "sphinx-autodoc-typehints", "sphinx-rtd-theme (>=0.2.5)"] +test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"] [[package]] -name = "platformdirs" -version = "4.4.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +name = "identify" +version = "2.6.13" +description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, - {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] +license = ["ukkonen"] [[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - [[package]] -name = "pytest" -version = "8.4.1" -description = "pytest: simple powerful testing with Python" +name = "ipykernel" +version = "6.30.1" +description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, - {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, + {file = "ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4"}, + {file = "ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b"}, ] [package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" +appnope = {version = ">=0.1.2", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=8.0.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = ">=1.4" +packaging = ">=22" +psutil = ">=5.7" +pyzmq = ">=25" +tornado = ">=6.2" +traitlets = ">=5.4.0" [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] +cov = ["coverage[toml]", "matplotlib", "pytest-cov", "trio"] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<9)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] [[package]] -name = "pytest-cov" -version = "6.2.1" -description = "Pytest plugin for measuring coverage." +name = "ipython" +version = "9.5.0" +description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.9" +python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, - {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, + {file = "ipython-9.5.0-py3-none-any.whl", hash = "sha256:88369ffa1d5817d609120daa523a6da06d02518e582347c29f8451732a9c5e72"}, + {file = "ipython-9.5.0.tar.gz", hash = "sha256:129c44b941fe6d9b82d36fc7a7c18127ddb1d6f02f78f867f402e2e3adde3113"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +ipython-pygments-lexers = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack_data = "*" +traitlets = ">=5.13.0" + +[package.extras] +all = ["ipython[doc,matplotlib,test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"] +matplotlib = ["matplotlib"] +test = ["packaging", "pytest", "pytest-asyncio", "testpath"] +test-extra = ["curio", "ipykernel", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbclient", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +description = "Defines a variety of Pygments lexers for highlighting IPython code." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"}, + {file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"}, +] + +[package.dependencies] +pygments = "*" + +[[package]] +name = "isort" +version = "6.0.1" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.9.0" +groups = ["dev"] +files = [ + {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, + {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, +] + +[package.extras] +colors = ["colorama"] +plugins = ["setuptools"] + +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0"}, + {file = "jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "loguru" +version = "0.7.3" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = "<4.0,>=3.5" +groups = ["base"] +files = [ + {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, + {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +files = [ + {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, + {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins (>=0.5.0)"] +profiling = ["gprof2dot"] +rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mypy" +version = "1.17.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "nbqa" +version = "1.9.1" +description = "Run any standard Python code quality tool on a Jupyter Notebook" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "nbqa-1.9.1-py3-none-any.whl", hash = "sha256:95552d2f6c2c038136252a805aa78d85018aef922586270c3a074332737282e5"}, + {file = "nbqa-1.9.1.tar.gz", hash = "sha256:a1f4bcf587c597302fed295951001fc4e1be4ce0e77e1ab1b25ac2fbe3db0cdd"}, +] + +[package.dependencies] +autopep8 = ">=1.5" +ipython = ">=7.8.0" +tokenize-rt = ">=3.2.0" +tomli = "*" + +[package.extras] +toolchain = ["black", "blacken-docs", "flake8", "isort", "jupytext", "mypy", "pylint", "pyupgrade", "ruff"] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +groups = ["dev"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "parso" +version = "0.8.5" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"}, + {file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "platformdirs" +version = "4.4.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "4.3.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, + {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, + {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, +] + +[package.extras] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, + {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, ] [package.dependencies] @@ -419,6 +1348,279 @@ pytest = ">=6.2.5" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-slugify" +version = "8.0.4" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, + {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + +[[package]] +name = "pywin32" +version = "311" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, + {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, + {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, + {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, + {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, + {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, + {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, + {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, + {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, + {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, + {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, + {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, + {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, + {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, + {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, + {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, + {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, + {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, + {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, + {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "pyzmq" +version = "27.0.2" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pyzmq-27.0.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:8b32c4636ced87dce0ac3d671e578b3400215efab372f1b4be242e8cf0b11384"}, + {file = "pyzmq-27.0.2-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9528a4b3e24189cb333a9850fddbbafaa81df187297cfbddee50447cdb042cf"}, + {file = "pyzmq-27.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b02ba0c0b2b9ebe74688002e6c56c903429924a25630804b9ede1f178aa5a3f"}, + {file = "pyzmq-27.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4dc5c9a6167617251dea0d024d67559795761aabb4b7ea015518be898be076"}, + {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1151b33aaf3b4fa9da26f4d696e38eebab67d1b43c446184d733c700b3ff8ce"}, + {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4ecfc7999ac44c9ef92b5ae8f0b44fb935297977df54d8756b195a3cd12f38f0"}, + {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:31c26a5d0b00befcaeeb600d8b15ad09f5604b6f44e2057ec5e521a9e18dcd9a"}, + {file = "pyzmq-27.0.2-cp310-cp310-win32.whl", hash = "sha256:25a100d2de2ac0c644ecf4ce0b509a720d12e559c77aff7e7e73aa684f0375bc"}, + {file = "pyzmq-27.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a1acf091f53bb406e9e5e7383e467d1dd1b94488b8415b890917d30111a1fef3"}, + {file = "pyzmq-27.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:b38e01f11e9e95f6668dc8a62dccf9483f454fed78a77447507a0e8dcbd19a63"}, + {file = "pyzmq-27.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:063845960df76599ad4fad69fa4d884b3ba38304272104fdcd7e3af33faeeb1d"}, + {file = "pyzmq-27.0.2-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:845a35fb21b88786aeb38af8b271d41ab0967985410f35411a27eebdc578a076"}, + {file = "pyzmq-27.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:515d20b5c3c86db95503faa989853a8ab692aab1e5336db011cd6d35626c4cb1"}, + {file = "pyzmq-27.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:862aedec0b0684a5050cdb5ec13c2da96d2f8dffda48657ed35e312a4e31553b"}, + {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cb5bcfc51c7a4fce335d3bc974fd1d6a916abbcdd2b25f6e89d37b8def25f57"}, + {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38ff75b2a36e3a032e9fef29a5871e3e1301a37464e09ba364e3c3193f62982a"}, + {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a5709abe8d23ca158a9d0a18c037f4193f5b6afeb53be37173a41e9fb885792"}, + {file = "pyzmq-27.0.2-cp311-cp311-win32.whl", hash = "sha256:47c5dda2018c35d87be9b83de0890cb92ac0791fd59498847fc4eca6ff56671d"}, + {file = "pyzmq-27.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f54ca3e98f8f4d23e989c7d0edcf9da7a514ff261edaf64d1d8653dd5feb0a8b"}, + {file = "pyzmq-27.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:2ef3067cb5b51b090fb853f423ad7ed63836ec154374282780a62eb866bf5768"}, + {file = "pyzmq-27.0.2-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:5da05e3c22c95e23bfc4afeee6ff7d4be9ff2233ad6cb171a0e8257cd46b169a"}, + {file = "pyzmq-27.0.2-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4520577971d01d47e2559bb3175fce1be9103b18621bf0b241abe0a933d040"}, + {file = "pyzmq-27.0.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d7de7bf73165b90bd25a8668659ccb134dd28449116bf3c7e9bab5cf8a8ec9"}, + {file = "pyzmq-27.0.2-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340e7cddc32f147c6c00d116a3f284ab07ee63dbd26c52be13b590520434533c"}, + {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba95693f9df8bb4a9826464fb0fe89033936f35fd4a8ff1edff09a473570afa0"}, + {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:ca42a6ce2d697537da34f77a1960d21476c6a4af3e539eddb2b114c3cf65a78c"}, + {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e44e665d78a07214b2772ccbd4b9bcc6d848d7895f1b2d7653f047b6318a4f6"}, + {file = "pyzmq-27.0.2-cp312-abi3-win32.whl", hash = "sha256:272d772d116615397d2be2b1417b3b8c8bc8671f93728c2f2c25002a4530e8f6"}, + {file = "pyzmq-27.0.2-cp312-abi3-win_amd64.whl", hash = "sha256:734be4f44efba0aa69bf5f015ed13eb69ff29bf0d17ea1e21588b095a3147b8e"}, + {file = "pyzmq-27.0.2-cp312-abi3-win_arm64.whl", hash = "sha256:41f0bd56d9279392810950feb2785a419c2920bbf007fdaaa7f4a07332ae492d"}, + {file = "pyzmq-27.0.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:7f01118133427cd7f34ee133b5098e2af5f70303fa7519785c007bca5aa6f96a"}, + {file = "pyzmq-27.0.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:e4b860edf6379a7234ccbb19b4ed2c57e3ff569c3414fadfb49ae72b61a8ef07"}, + {file = "pyzmq-27.0.2-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:cb77923ea163156da14295c941930bd525df0d29c96c1ec2fe3c3806b1e17cb3"}, + {file = "pyzmq-27.0.2-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:61678b7407b04df8f9423f188156355dc94d0fb52d360ae79d02ed7e0d431eea"}, + {file = "pyzmq-27.0.2-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3c824b70925963bdc8e39a642672c15ffaa67e7d4b491f64662dd56d6271263"}, + {file = "pyzmq-27.0.2-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4833e02fcf2751975457be1dfa2f744d4d09901a8cc106acaa519d868232175"}, + {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b18045668d09cf0faa44918af2a67f0dbbef738c96f61c2f1b975b1ddb92ccfc"}, + {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bbbb7e2f3ac5a22901324e7b086f398b8e16d343879a77b15ca3312e8cd8e6d5"}, + {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b751914a73604d40d88a061bab042a11d4511b3ddbb7624cd83c39c8a498564c"}, + {file = "pyzmq-27.0.2-cp313-cp313t-win32.whl", hash = "sha256:3e8f833dd82af11db5321c414638045c70f61009f72dd61c88db4a713c1fb1d2"}, + {file = "pyzmq-27.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5b45153cb8eadcab14139970643a84f7a7b08dda541fbc1f6f4855c49334b549"}, + {file = "pyzmq-27.0.2-cp313-cp313t-win_arm64.whl", hash = "sha256:86898f5c9730df23427c1ee0097d8aa41aa5f89539a79e48cd0d2c22d059f1b7"}, + {file = "pyzmq-27.0.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d2b4b261dce10762be5c116b6ad1f267a9429765b493c454f049f33791dd8b8a"}, + {file = "pyzmq-27.0.2-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4d88b6cff156fed468903006b24bbd85322612f9c2f7b96e72d5016fd3f543"}, + {file = "pyzmq-27.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8426c0ebbc11ed8416a6e9409c194142d677c2c5c688595f2743664e356d9e9b"}, + {file = "pyzmq-27.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565bee96a155fe6452caed5fb5f60c9862038e6b51a59f4f632562081cdb4004"}, + {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5de735c745ca5cefe9c2d1547d8f28cfe1b1926aecb7483ab1102fd0a746c093"}, + {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ea4f498f8115fd90d7bf03a3e83ae3e9898e43362f8e8e8faec93597206e15cc"}, + {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d00e81cb0afd672915257a3927124ee2ad117ace3c256d39cd97ca3f190152ad"}, + {file = "pyzmq-27.0.2-cp314-cp314t-win32.whl", hash = "sha256:0f6e9b00d81b58f859fffc112365d50413954e02aefe36c5b4c8fb4af79f8cc3"}, + {file = "pyzmq-27.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2e73cf3b127a437fef4100eb3ac2ebe6b49e655bb721329f667f59eca0a26221"}, + {file = "pyzmq-27.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:4108785f2e5ac865d06f678a07a1901e3465611356df21a545eeea8b45f56265"}, + {file = "pyzmq-27.0.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:59a50f5eedf8ed20b7dbd57f1c29b2de003940dea3eedfbf0fbfea05ee7f9f61"}, + {file = "pyzmq-27.0.2-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:a00e6390e52770ba1ec753b2610f90b4f00e74c71cfc5405b917adf3cc39565e"}, + {file = "pyzmq-27.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49d8d05d9844d83cddfbc86a82ac0cafe7ab694fcc9c9618de8d015c318347c3"}, + {file = "pyzmq-27.0.2-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3660d85e2b6a28eb2d586dedab9c61a7b7c64ab0d89a35d2973c7be336f12b0d"}, + {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:bccfee44b392f4d13bbf05aa88d8f7709271b940a8c398d4216fde6b717624ae"}, + {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:989066d51686415f1da646d6e2c5364a9b084777c29d9d1720aa5baf192366ef"}, + {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc283595b82f0db155a52f6462945c7b6b47ecaae2f681746eeea537c95cf8c9"}, + {file = "pyzmq-27.0.2-cp38-cp38-win32.whl", hash = "sha256:ad38daf57495beadc0d929e8901b2aa46ff474239b5a8a46ccc7f67dc01d2335"}, + {file = "pyzmq-27.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:36508466a266cf78bba2f56529ad06eb38ba827f443b47388d420bec14d331ba"}, + {file = "pyzmq-27.0.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:aa9c1c208c263b84386ac25bed6af5672397dc3c232638114fc09bca5c7addf9"}, + {file = "pyzmq-27.0.2-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:795c4884cfe7ea59f2b67d82b417e899afab889d332bfda13b02f8e0c155b2e4"}, + {file = "pyzmq-27.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47eb65bb25478358ba3113dd9a08344f616f417ad3ffcbb190cd874fae72b1b1"}, + {file = "pyzmq-27.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6fc24f00293f10aff04d55ca37029b280474c91f4de2cad5e911e5e10d733b7"}, + {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58d4cc9b6b768478adfc40a5cbee545303db8dbc81ba688474e0f499cc581028"}, + {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea2f26c5972796e02b222968a21a378d09eb4ff590eb3c5fafa8913f8c2bdf5"}, + {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a0621ec020c49fc1b6e31304f1a820900d54e7d9afa03ea1634264bf9387519e"}, + {file = "pyzmq-27.0.2-cp39-cp39-win32.whl", hash = "sha256:1326500792a9cb0992db06bbaf5d0098459133868932b81a6e90d45c39eca99d"}, + {file = "pyzmq-27.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:5ee9560cb1e3094ef01fc071b361121a57ebb8d4232912b6607a6d7d2d0a97b4"}, + {file = "pyzmq-27.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:85e3c6fb0d25ea046ebcfdc2bcb9683d663dc0280645c79a616ff5077962a15b"}, + {file = "pyzmq-27.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d67a0960803a37b60f51b460c58444bc7033a804c662f5735172e21e74ee4902"}, + {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:dd4d3e6a567ffd0d232cfc667c49d0852d0ee7481458a2a1593b9b1bc5acba88"}, + {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e558be423631704803bc6a642e2caa96083df759e25fe6eb01f2d28725f80bd"}, + {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4c20ba8389f495c7b4f6b896bb1ca1e109a157d4f189267a902079699aaf787"}, + {file = "pyzmq-27.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c5be232f7219414ff672ff7ab8c5a7e8632177735186d8a42b57b491fafdd64e"}, + {file = "pyzmq-27.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e297784aea724294fe95e442e39a4376c2f08aa4fae4161c669f047051e31b02"}, + {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e3659a79ded9745bc9c2aef5b444ac8805606e7bc50d2d2eb16dc3ab5483d91f"}, + {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3dba49ff037d02373a9306b58d6c1e0be031438f822044e8767afccfdac4c6b"}, + {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de84e1694f9507b29e7b263453a2255a73e3d099d258db0f14539bad258abe41"}, + {file = "pyzmq-27.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f0944d65ba2b872b9fcece08411d6347f15a874c775b4c3baae7f278550da0fb"}, + {file = "pyzmq-27.0.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:05288947797dcd6724702db2056972dceef9963a83041eb734aea504416094ec"}, + {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:dff9198adbb6810ad857f3bfa59b4859c45acb02b0d198b39abeafb9148474f3"}, + {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849123fd9982c7f63911fdceba9870f203f0f32c953a3bab48e7f27803a0e3ec"}, + {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5ee06945f3069e3609819890a01958c4bbfea7a2b31ae87107c6478838d309e"}, + {file = "pyzmq-27.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6156ad5e8bbe8a78a3f5b5757c9a883b0012325c83f98ce6d58fcec81e8b3d06"}, + {file = "pyzmq-27.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:400f34321e3bd89b1165b91ea6b18ad26042ba9ad0dfed8b35049e2e24eeab9b"}, + {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9cbad4ef12e4c15c94d2c24ecd15a8ed56bf091c62f121a2b0c618ddd4b7402b"}, + {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6b2b74aac3392b8cf508ccb68c980a8555298cd378434a2d065d6ce0f4211dff"}, + {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7db5db88c24cf9253065d69229a148ff60821e5d6f8ff72579b1f80f8f348bab"}, + {file = "pyzmq-27.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8ffe40c216c41756ca05188c3e24a23142334b304f7aebd75c24210385e35573"}, + {file = "pyzmq-27.0.2.tar.gz", hash = "sha256:b398dd713b18de89730447347e96a0240225e154db56e35b6bb8447ffdb07798"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "14.1.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["main", "dev"] +files = [ + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "ruff" version = "0.12.11" @@ -448,25 +1650,254 @@ files = [ {file = "ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d"}, ] +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + +[[package]] +name = "tokenize-rt" +version = "6.2.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44"}, + {file = "tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6"}, +] + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "tornado" +version = "6.5.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6"}, + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04"}, + {file = "tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0"}, + {file = "tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f"}, + {file = "tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af"}, + {file = "tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typer" +version = "0.16.1" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "typer-0.16.1-py3-none-any.whl", hash = "sha256:90ee01cb02d9b8395ae21ee3368421faf21fa138cb2a541ed369c08cec5237c9"}, + {file = "typer-0.16.1.tar.gz", hash = "sha256:d358c65a464a7a90f338e3bb7ff0c74ac081449e53884b12ba658cbd72990614"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20250822" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_python_dateutil-2.9.0.20250822-py3-none-any.whl", hash = "sha256:849d52b737e10a6dc6621d2bd7940ec7c65fcb69e6aa2882acf4e56b2b508ddc"}, + {file = "types_python_dateutil-2.9.0.20250822.tar.gz", hash = "sha256:84c92c34bd8e68b117bff742bc00b692a1e8531262d4507b33afcc9f7716cd53"}, +] + [[package]] name = "typing-extensions" version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.34.0" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, + {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + [[package]] name = "win32-setctime" version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" -groups = ["dev"] +groups = ["base"] markers = "sys_platform == \"win32\"" files = [ {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, @@ -479,4 +1910,4 @@ dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [metadata] lock-version = "2.1" python-versions = "^3.13" -content-hash = "8ccac5d639fed154404e3c3d223ce4f7e13663b72a07ee73deed1d4a52bd84b8" +content-hash = "239cc0b9f4ea97d809d2f6310a0b5cfb77239aed8f527e830bfc95ea687025e1" diff --git a/pyproject.toml b/pyproject.toml index c73ebf3..a0725cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,16 +7,24 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.13" +typer = "^0.16.1" -[tool.poetry.group.dev.dependencies] +[tool.poetry.group.base.dependencies] anytree = "^2.13.0" -black = "*" -isort = "*" -loguru = "*" -mypy = "*" -pytest = "*" -pytest-cov = "*" -ruff = "*" +loguru = "^0.7.3" + +[tool.poetry.group.dev.dependencies] +black = "^25.1.0" +cookiecutter = "^2.6.0" +graphviz = "^0.21" +ipykernel = "^6.30.1" +isort = "^6.0.1" +mypy = "^1.17.1" +nbqa = "^1.9.1" +pre-commit = "^4.3.0" +pytest = "^8.4.1" +pytest-cov = "^6.2.1" +ruff = "^0.12.11" [build-system] requires = ["poetry-core"] @@ -31,7 +39,7 @@ target-version = ['py312'] include = '.*\.(py|ipynb)$' # All .py and .ipynb files extend-exclude = ''' /( - leetcode/_template + .templates )/ ''' @@ -43,12 +51,12 @@ force_grid_wrap = 0 combine_as_imports = true use_parentheses = true ensure_newline_before_comments = true -extend_skip_glob = ["leetcode/_template/*"] +extend_skip_glob = [".templates/*"] [tool.ruff] line-length = 105 target-version = 'py312' -extend-exclude = ["leetcode/_template"] +extend-exclude = [".templates"] [tool.ruff.lint.pydocstyle] convention = "numpy" @@ -58,11 +66,11 @@ files = '**/*.py' warn_unused_configs = true ignore_missing_imports = true disable_error_code = ["return", "no-redef"] -exclude = ["leetcode/_template"] +exclude = [".templates"] [tool.pytest.ini_options] -testpaths = ["leetcode"] -python_files = ["tests.py"] +testpaths = ["leetcode", "tests"] +python_files = ["tests.py", "test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] addopts = "-v --tb=short" diff --git a/sonar-project.properties b/sonar-project.properties index 02993f7..cb6e2c2 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -8,7 +8,7 @@ sonar.projectVersion=1.0 sonar.sources=leetcode,leetcode_py sonar.tests=leetcode sonar.test.inclusions=**/tests.py -sonar.exclusions=**/conftest.py,**/_template/**,**/__pycache__/**,**/.venv/** +sonar.exclusions=**/conftest.py,**/.templates/**,**/__pycache__/**,**/.venv/** # Python specific settings sonar.python.version=3.13 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..d4839a6 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Tests package diff --git a/tests/test_list_node.py b/tests/test_list_node.py new file mode 100644 index 0000000..12cc04e --- /dev/null +++ b/tests/test_list_node.py @@ -0,0 +1,105 @@ +import pytest + +from leetcode_py.list_node import ListNode + + +class TestListNode: + @pytest.mark.parametrize( + "val,expected_val,expected_next", + [ + (None, 0, None), # default + (5, 5, None), # with value + ], + ) + def test_init(self, val, expected_val, expected_next): + node = ListNode() if val is None else ListNode(val) + assert node.val == expected_val + assert node.next == expected_next + + def test_init_with_next(self): + next_node = ListNode(2) + node = ListNode(1, next_node) + assert node.val == 1 + assert node.next == next_node + + @pytest.mark.parametrize( + "input_list,expected_result", + [ + ([], None), + ([1], "single_node"), + ([1, 2, 3], "multiple_nodes"), + ], + ) + def test_from_list(self, input_list, expected_result): + result = ListNode.from_list(input_list) + + if expected_result is None: + assert result is None + elif expected_result == "single_node": + assert result is not None + assert result.val == 1 + assert result.next is None + elif expected_result == "multiple_nodes": + assert result is not None + assert result.val == 1 + assert result.next is not None + assert result.next.val == 2 + assert result.next.next is not None + assert result.next.next.val == 3 + assert result.next.next.next is None + + @pytest.mark.parametrize( + "input_list,expected_output", + [ + ([1], [1]), + ([1, 2, 3], [1, 2, 3]), + ], + ) + def test_to_list(self, input_list, expected_output): + node = ListNode.from_list(input_list) + assert node is not None + assert node.to_list() == expected_output + + @pytest.mark.parametrize( + "input_list,expected_str,expected_repr", + [ + ([1, 2, 3], "1 -> 2 -> 3", "ListNode([1, 2, 3])"), + ], + ) + def test_string_representations(self, input_list, expected_str, expected_repr): + node = ListNode.from_list(input_list) + assert node is not None + assert str(node) == expected_str + assert repr(node) == expected_repr + assert node._repr_html_() == expected_str + + @pytest.mark.parametrize( + "list1,list2,should_equal", + [ + ([1, 2, 3], [1, 2, 3], True), + ([1, 2, 3], [1, 2, 4], False), + ], + ) + def test_equality(self, list1, list2, should_equal): + node1 = ListNode.from_list(list1) + node2 = ListNode.from_list(list2) + assert (node1 == node2) == should_equal + + @pytest.mark.parametrize("other_value", [[1], "1"]) + def test_equality_different_types(self, other_value): + node = ListNode(1) + assert node != other_value + + @pytest.mark.parametrize( + "test_list", + [ + [1, 2, 3, 4, 5], + [1], + [10, 20, 30], + ], + ) + def test_roundtrip_conversion(self, test_list): + node = ListNode.from_list(test_list) + assert node is not None + result = node.to_list() + assert result == test_list diff --git a/tests/test_test_utils.py b/tests/test_test_utils.py new file mode 100644 index 0000000..31533cd --- /dev/null +++ b/tests/test_test_utils.py @@ -0,0 +1,71 @@ +from unittest.mock import patch + +import pytest + +from leetcode_py.test_utils import logged_test + + +class TestLoggedTest: + def test_logged_test_decorator_success(self): + @logged_test + def sample_test(): + return "success" + + result = sample_test() + assert result == "success" + + def test_logged_test_decorator_failure(self): + @logged_test + def failing_test(): + raise ValueError("test error") + + with pytest.raises(ValueError, match="test error"): + failing_test() + + def test_logged_test_preserves_function_metadata(self): + @logged_test + def sample_function(): + """Sample docstring""" + pass + + assert sample_function.__name__ == "sample_function" + assert sample_function.__doc__ == "Sample docstring" + + @pytest.mark.parametrize( + "args,kwargs,expected", + [ + ((1, 2), {"c": 3}, 6), + ((5, 10), {}, 15), + ], + ) + def test_logged_test_with_arguments(self, args, kwargs, expected): + @logged_test + def function_with_args(a, b, c=None): + return a + b + (c or 0) + + result = function_with_args(*args, **kwargs) + assert result == expected + + @pytest.mark.parametrize( + "kwargs,expected", + [ + ({"a": 1, "b": 2, "c": 3}, 6), + ({"x": 10, "y": 20}, 30), + ], + ) + def test_logged_test_with_kwargs(self, kwargs, expected): + @logged_test + def function_with_kwargs(**kwargs): + return sum(kwargs.values()) + + result = function_with_kwargs(**kwargs) + assert result == expected + + @patch("builtins.print") + def test_logged_test_prints_empty_line(self, mock_print): + @logged_test + def sample_test(): + return "success" + + sample_test() + mock_print.assert_called_once_with("") diff --git a/tests/test_tree_node.py b/tests/test_tree_node.py new file mode 100644 index 0000000..3f872f3 --- /dev/null +++ b/tests/test_tree_node.py @@ -0,0 +1,161 @@ +import pytest + +from leetcode_py.tree_node import TreeNode, build_anytree + + +class TestTreeNode: + @pytest.mark.parametrize( + "val,expected_val", + [ + (None, 0), # default + (5, 5), # with value + ], + ) + def test_init(self, val, expected_val): + node = TreeNode() if val is None else TreeNode(val) + assert node.val == expected_val + assert node.left is None + assert node.right is None + + def test_init_with_children(self): + left = TreeNode(1) + right = TreeNode(2) + node = TreeNode(0, left, right) + assert node.val == 0 + assert node.left == left + assert node.right == right + + def test_from_list_empty(self): + result = TreeNode.from_list([]) + assert result is None + + def test_from_list_none_root(self): + result = TreeNode.from_list([None]) + assert result is None + + def test_from_list_single(self): + result = TreeNode.from_list([1]) + assert result is not None + assert result.val == 1 + assert result.left is None + assert result.right is None + + def test_from_list_complete_tree(self): + result = TreeNode.from_list([1, 2, 3, 4, 5, 6, 7]) + assert result is not None + assert result.val == 1 + assert result.left is not None + assert result.left.val == 2 + assert result.right is not None + assert result.right.val == 3 + assert result.left.left is not None + assert result.left.left.val == 4 + assert result.left.right is not None + assert result.left.right.val == 5 + assert result.right.left is not None + assert result.right.left.val == 6 + assert result.right.right is not None + assert result.right.right.val == 7 + + def test_from_list_sparse_tree(self): + result = TreeNode.from_list([1, None, 2]) + assert result is not None + assert result.val == 1 + assert result.left is None + assert result.right is not None + assert result.right.val == 2 + assert result.right.left is None + assert result.right.right is None + + @pytest.mark.parametrize( + "input_list,expected_output", + [ + ([1], [1]), + ([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7]), + ([1, None, 2], [1, None, 2]), + ], + ) + def test_to_list(self, input_list, expected_output): + node = TreeNode.from_list(input_list) + assert node is not None + assert node.to_list() == expected_output + + @pytest.mark.parametrize( + "input_list,expected_values", + [ + ([1], ["1"]), + ([1, 2, 3], ["1", "2", "3"]), + ], + ) + def test_str_representation(self, input_list, expected_values): + node = TreeNode.from_list(input_list) + assert node is not None + result = str(node) + for val in expected_values: + assert val in result + + def test_repr_representation(self): + node = TreeNode.from_list([1, 2, 3]) + assert node is not None + assert repr(node) == "TreeNode([1, 2, 3])" + + def test_repr_html_generates_svg(self): + node = TreeNode.from_list([1, 2, 3]) + assert node is not None + result = node._repr_html_() + assert isinstance(result, str) + assert "svg" in result.lower() + + @pytest.mark.parametrize( + "list1,list2,should_equal", + [ + ([1, 2, 3], [1, 2, 3], True), + ([1, 2, 3], [1, 3, 2], False), + ], + ) + def test_equality(self, list1, list2, should_equal): + node1 = TreeNode.from_list(list1) + node2 = TreeNode.from_list(list2) + assert node1 is not None + assert node2 is not None + assert (node1 == node2) == should_equal + + @pytest.mark.parametrize("other_value", [[1], "1"]) + def test_equality_different_types(self, other_value): + node = TreeNode(1) + assert node != other_value + + @pytest.mark.parametrize( + "test_list", + [ + [1, 2, 3, 4, 5, None, 6], + [1], + [1, None, 2], + ], + ) + def test_roundtrip_conversion(self, test_list): + node = TreeNode.from_list(test_list) + assert node is not None + result = node.to_list() + assert result == test_list + + def test_build_anytree_none(self): + result = build_anytree(None) + assert result is None + + def test_build_anytree_single_node(self): + node = TreeNode(1) + result = build_anytree(node) + assert result is not None + assert result.name == "1" + assert len(result.children) == 0 + + def test_str_with_none_tree(self): + # Create a scenario where build_anytree returns None + # This happens when we have a node but build_anytree fails + import unittest.mock + + with unittest.mock.patch("leetcode_py.tree_node.build_anytree", return_value=None): + node = TreeNode(1) + result = str(node) + assert result == "None"