From 81bbfc7839de06222d63ead975e9a09f98de3514 Mon Sep 17 00:00:00 2001 From: Wisaroot <66859294+wisarootl@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:27:04 +0700 Subject: [PATCH] feat: re-architect repo (#10) --- .amazonq/plan/compare_template_files.py | 98 + .amazonq/plan/cookiecutter-template-plan.md | 224 +++ .amazonq/rules/development-rules.md | 58 +- .amazonq/rules/problem-creation.md | 31 + .github/workflows/ci-test.yml | 11 + .gitignore | 10 +- .templates/leetcode/cookiecutter.json | 41 + .templates/leetcode/examples/README.md | 71 + .templates/leetcode/examples/basic.json5 | 61 + .../leetcode/examples/linked_list.json5 | 62 + .templates/leetcode/examples/matrix.json5 | 59 + .templates/leetcode/examples/string.json5 | 61 + .templates/leetcode/examples/tree.json5 | 63 + .templates/leetcode/gen.py | 151 ++ .templates/leetcode/json/insert_interval.json | 68 + .../leetcode/json/invert_binary_tree.json | 37 + .../leetcode/json/reverse_linked_list_ii.json | 35 + .../{{cookiecutter.problem_name}}/README.md | 29 + .../__init__.py | 0 .../playground.ipynb | 70 + .../{{cookiecutter.problem_name}}/solution.py | 9 + .../{{cookiecutter.problem_name}}/tests.py | 42 + Makefile | 56 +- README.md | 153 +- docs/images/linkedlist-viz.png | Bin 0 -> 30293 bytes docs/images/notebook-example.png | Bin 0 -> 86386 bytes docs/images/tree-viz.png | Bin 0 -> 55495 bytes leetcode/_template/README.md | 44 - leetcode/_template/solution.py | 9 - leetcode/_template/tests.py | 24 - leetcode/insert_interval/README.md | 39 + leetcode/insert_interval/__init__.py | 0 leetcode/insert_interval/playground.ipynb | 80 + leetcode/insert_interval/solution.py | 22 + leetcode/insert_interval/tests.py | 29 + leetcode/invert_binary_tree/README.md | 4 +- leetcode/invert_binary_tree/playground.ipynb | 173 ++ leetcode/invert_binary_tree/solution.py | 6 +- leetcode/invert_binary_tree/tests.py | 25 +- leetcode/reverse_linked_list_ii/README.md | 33 + leetcode/reverse_linked_list_ii/__init__.py | 0 .../reverse_linked_list_ii/playground.ipynb | 86 + leetcode/reverse_linked_list_ii/solution.py | 30 + leetcode/reverse_linked_list_ii/tests.py | 31 + leetcode/two_sum/README.md | 51 - leetcode/two_sum/solution.py | 14 - leetcode/two_sum/tests.py | 26 - leetcode_py/list_node.py | 37 + leetcode_py/test_utils.py | 6 +- leetcode_py/tree_node.py | 31 + poetry.lock | 1733 +++++++++++++++-- pyproject.toml | 36 +- sonar-project.properties | 2 +- tests/__init__.py | 1 + tests/test_list_node.py | 105 + tests/test_test_utils.py | 71 + tests/test_tree_node.py | 161 ++ 57 files changed, 3968 insertions(+), 441 deletions(-) create mode 100644 .amazonq/plan/compare_template_files.py create mode 100644 .amazonq/plan/cookiecutter-template-plan.md create mode 100644 .amazonq/rules/problem-creation.md create mode 100644 .templates/leetcode/cookiecutter.json create mode 100644 .templates/leetcode/examples/README.md create mode 100644 .templates/leetcode/examples/basic.json5 create mode 100644 .templates/leetcode/examples/linked_list.json5 create mode 100644 .templates/leetcode/examples/matrix.json5 create mode 100644 .templates/leetcode/examples/string.json5 create mode 100644 .templates/leetcode/examples/tree.json5 create mode 100644 .templates/leetcode/gen.py create mode 100644 .templates/leetcode/json/insert_interval.json create mode 100644 .templates/leetcode/json/invert_binary_tree.json create mode 100644 .templates/leetcode/json/reverse_linked_list_ii.json create mode 100644 .templates/leetcode/{{cookiecutter.problem_name}}/README.md rename {leetcode/two_sum => .templates/leetcode/{{cookiecutter.problem_name}}}/__init__.py (100%) create mode 100644 .templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb create mode 100644 .templates/leetcode/{{cookiecutter.problem_name}}/solution.py create mode 100644 .templates/leetcode/{{cookiecutter.problem_name}}/tests.py create mode 100644 docs/images/linkedlist-viz.png create mode 100644 docs/images/notebook-example.png create mode 100644 docs/images/tree-viz.png delete mode 100644 leetcode/_template/README.md delete mode 100644 leetcode/_template/solution.py delete mode 100644 leetcode/_template/tests.py create mode 100644 leetcode/insert_interval/README.md create mode 100644 leetcode/insert_interval/__init__.py create mode 100644 leetcode/insert_interval/playground.ipynb create mode 100644 leetcode/insert_interval/solution.py create mode 100644 leetcode/insert_interval/tests.py create mode 100644 leetcode/invert_binary_tree/playground.ipynb create mode 100644 leetcode/reverse_linked_list_ii/README.md create mode 100644 leetcode/reverse_linked_list_ii/__init__.py create mode 100644 leetcode/reverse_linked_list_ii/playground.ipynb create mode 100644 leetcode/reverse_linked_list_ii/solution.py create mode 100644 leetcode/reverse_linked_list_ii/tests.py delete mode 100644 leetcode/two_sum/README.md delete mode 100644 leetcode/two_sum/solution.py delete mode 100644 leetcode/two_sum/tests.py create mode 100644 leetcode_py/list_node.py create mode 100644 tests/__init__.py create mode 100644 tests/test_list_node.py create mode 100644 tests/test_test_utils.py create mode 100644 tests/test_tree_node.py 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 0000000000000000000000000000000000000000..46df3b5c81fdde319d5b931eca5706d32505d85f GIT binary patch literal 30293 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 literal 0 HcmV?d00001 diff --git a/docs/images/notebook-example.png b/docs/images/notebook-example.png new file mode 100644 index 0000000000000000000000000000000000000000..64d848e37ddaf2201ce80e6953575e362510d1f6 GIT binary patch literal 86386 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 T;NVhYV*mnAS3j3^P67Buj?-jhWb~SM zZWxl0QFM@zo&0i&ic}&sKZPPAJ7Mjnp<$q@p}}Y10|vQyI+KxIdFk~+`3|`{(?hh| z^&6i~S(-oQ`gr~F(~qa*SS4>V8DHOsR-LexbKzffX;!?e$f@X|sLrWPdxk+|RBN4* zg*L=Pl9dBBnVorK*4bZ$2b?xZUH6@?pH_wJQzEeVtn}LO_EV0)Tbxu(YNx_uCY4fk z`d?C_TZ2qC^1H$nuc84`F&pLK!LPWLbTMfKU3L*8?gN=}g-7bL@0*s$dYJC}jC@XP zd>kUm@ib81r+-1Cn>}H`oky8PuwvpB z4Gp>-zml3r3g;b!yyZ^EU8+twk&274N?fn~vPQ(-UCO;lLkB#?d|N_KR!I6N$Hqo@ zB(J1D3N;2xour)M&aW=5qHjCV?lCOh9|)8#8q;ndsXNu#Toa_LOLmp?e2R?n#9194b-&x8DvKWkHT z*XJx6PkvXzL9KY z#?9E=&s z&-YgqKM!Sob6o>I4X}?hpPZP4m;}GdX+AzaB_Agc!0^VczsX5|Df7Gf`P~DEiw6V* zhy_TCfqh)WB^4AD#3iJ}rKChjHAH=by!{*kMZJA5{-=?j?c8wo1^T$%^K%1x^ZjVo z!4VAcQ|9OY!RXh|fBNYh==NVu-oAf}MG{c_#~X1;F$wWMk@>lS{x@Vl-u#E`FS-81 zPU%Nu025zl9}TdVm$SE@%6~Pk^cSc9<>i0-`5!_9w?Jo4^BZm?N?(#pDw0xCQh$^E z^VJ_oE&fX?FMaKI%HQ7nM)^YqfT6Q5*c0+YhbG=`ekvq}e=7bbmHB_kR3v3lbG z`@Q_1G#39Gn%~R+Nu%%MMpC20kM61bT{>Z7Qdg#D7_? z%IOcCRFh<6YGj%>)QtmAtj|)1a(%+R+Y+=&wZ3HW2|5!Z-ZURAX`jV<{ba!dy|b2) zFCK6N$Eu26ih4njXkd6MO6{axq=6TZ`Du-$Sku0kylk&~>e6vbihq3oKr#=l{+;?IcCV zm+czgkw0~Wq9c&;4?REiv6w8XrYUrNM&_SF{b+?G7WtoA`ya9XJ8u6oTEE%V{|82> z#!pc|Q&i;qz@M!C%pc|^|5XF)WZqF+7aQ%-8vHw3apQw-K&1W{qfA~SZ!;>SiF1>CG@ZODAK0)iqF`TVL`+Cq~ z%iUqIn&(RLE`=2{HHbObx*8vfBG{=eElZEvaTcX~Y<+>cM;M4@)o9D(&5=-}f@r@? zFx_}5e;!MR4`lc?jox1-xkzhhw!8pk{J^R}QY{oSGhJ>a*L~oq@}2OZ&Mk>gft}mQ z%(l#!TV)*qzlkc1Tf3^N6lR{|i?qrXjyT%P1WdO!d=^A=JFtt|!u5}ELju*Z1EtEE z0$sR5X6ieJ({fN{#HdT&@lzG-o~BnZ*z|Vu)@b|ohZR=584V^l>*J->kRJUTXU{tA zr-8epxN+25@28mbc+yVFXG@t=IRzvhCZ3LVl--O>e5#SEiCB-90*~1Wi72q8ZPI&l z{2ef~2&&olg$z7k4KlZP1f9pBH)b$CgvoA5X7Wlgbtk#ny6;Q?F*sG~C@1eJ{YV0-I&Si-? z33Wf&CISy`$w;7F7rr#TpTO8QoCH#0mV2L`uX_IYhD;UvOHSH*FM0dFx_DJqyh#j5 z+c<4d2mxoJWN&-(s!YM2NFU4J-WI+Z4J%08)_@Kk>ZHr?83|-oi|qJ@*xkR^Nd-A^ z^}^5SO!pk01nR~$hG+UwOAjeu*;)Eb1-Kg8is6=bJk)rU- z|4zV2j`#Y!NQNWy(Md{VCJ^fHQJ}P?1$k>Y;M;MZcq8UsWpU{4m|nT?()+Ox<=I8> zb1cX?*!7~_LEV<8Bmo2xo4Iga9}A@Hlt}{JXO_mS?@$tgU$@-Y6U_sfpb?iFP7bzn z7>IA5Y();hcviV9Xc1tkON?#+p-*SNXLZYry-QEs)LRpZNN*jQ>^%81BxDjLxv-(c z71*hd>F?_9&)$IFb;l3K@%mz!^YAmDdwDAwJF;Pibpx9=iA&_DA01`8y-R; z%4~-BFscII0@{aKiz_u36kU7Whr&hC8bT8W8W`87^a`=7iIhPECrx$R9M}f^ ztj;p3F=RJ!j$2U^lg)`<@|R?F``5-g=R8}*q$gcHp4)hc+-4|m?Oa*Q zsC?T;*LDbxsC>P@Cxcn12IcQvq;l385aV7_vnZ~ZGrV$~+h~yr_MEJF@hD@x7_7H? z`Cu>b0LNl@|7}mH&Fu_EuguRSUg68Ql>Ro}Toq%f;lxUuWDGPLJD#n4{VBt!KP|wi z!57rQv*dbf3U6G$4v_m+}F-145Q;TOPq;&{P?cEZ+q?0 z?||$%KE~p`zF_r?mTxGIA>2a3`h}Q~((buvpA3(aW)UG^o7V9W?p1;LjG-DBe2ssk z-=L?x=jvqLN+jKSCclQq6j#qy!6YON~0rMzcfxM{qj9|uo zyTjzsP+qad_z{SB1EXZZ;%r#ADm)3mC@6$>1(>$yj#To@@uXI-)XMFwcrkv2)1sLDB z3DyoeV$j5>+Cn=+H4G2p7FR-1H*YXAj56XPBkR;of)ZE8B-}wOJ4Cx%gJlKO6jpO?&jzi zE3+EB*m$Xa;0#K-@=BL%gI(4^wWhPC*CV$aawJt4qccO!RiW0~ce4DkOOYzT9iAR5 z{Qdkjm1!Aa!``_s+xgUKo51h@-L8rw3n^cMsiw^4vn_TDDOq?qz7=)1AuYK}QL(Xi zTz0Z&gLZ9Ghscqg2=cP6M;NrpAmqu%AiQa)svMooDdb@r))SgH-OC_tI^iy@^DcX~ z*(ZN^zqP6}Cw;_bmrWGdhg1h6_nxWNIAMA_I!ESlO2qUc=tho2opgTE`y=<(V zzqR|m2GnU4_J}-Fg$-1kN0X`KyJM`0F(!WQol|{nmYEGGZfw~-0_2gUv?9u?rTh2> z(K$nCz~Pt$=UN=2KWaDJluS#wi5z%70{G%m=2pXDZ(L^F)%`uSC`tM*n?Rr0cM}I# z0w-M@L}I~@xKUDw(UBRPraMxdwI;+Z9Q5E2N7gb-OJW=kj5O#E={IIA;-zi)vBP71 zY=Bo0FddGUHi91pstT)_sX^DutuiIzr}hq|ge6KWfS(WfvXwWoh8NOla~h^-m!Z(G zIrO?o-uQ0&ZOv--G>hr0jOXqgk9coSR;!n<-!JVkLmXBao1myv;JPYSi6d}}_KJbc zoMqp*)?wfp<*@G*W5rlBgmxW?@*wotN^L(yXZjkue3?{~eNouy6?E)2xi@6Dj#R0} zN(W!_5*t7Z%_8=j{MG^a4e>M^5FQEj#9=5hDEG|1GRpLw(3^*(qZgxWF3 zk7pI#n?0`=7E+)KcVZ9zT$m`i>l%`TdvjvEidir1(1f@5O`5m7`f!X20h`h<5LvOX z>eekA_0_)jrN9-?as2m>hw3YM&D+{qNM3_oE+BHV51gTTWDPiGo(#+fJT5}WN62|P z<#gMZq^sUook#&5!wp49t8h@~nQyjQ$a@9j}R)wF*~O84z$anwOV5chmzX?Mf0 z=iAdsxOv`r4)L;@vdoMVWeG)(b;K>@u7%Af^B!$SeGdHide(RhkGoz@KJmH zpo~y=6^iEGFSnozGZ0S8jflyy;r^M3x5c3Y-jj!4cG^uD&B4#vF}(~hSBYhEX-=!R zF++`#JOk+PiaLF7j~uH`L96;^11ZC#3j6prXBobFUvODpcB9Zd?qdD@xkT&L;pSFI zv#pJ8s-x~+`dmZ?yY-Y9fYv4dm4M~ASWF0n!5tb|>Ye_D;*-pGTs`|oOEkKW%E>$m zRwKZM-{I9yBwwn7Vssr^ zMiW(o6aeZxGIco@FUu_R02A}=2&rBCmSA6Ambseb4lTVd#Bom18q#$-lS^vSDnsDw zD+{ge9N!|s=#DdC1_3CYZvCb@O!z2V8OM*5IDO7_C^9ougXOYe-Tjxoo{j`#_J9w< zsAdRovaQmv>9h8bNGZu|EvE_Dq0f`guO})C@QoU*dP%j(yelqCa2AMJv%jBPH?Ssz zHH2Pjr_ir+9lQembQ=Xd68CMQSx(OCLq*c4t+YjgfIDs;JaZZsveZMjQ0x8bA> zN49bGo0uh2f%N{WjQPyJ3D2!O=ycZu)W}2FeNcFVdMscd^RT)y>vAtw z`OV{dMTp+|&=+tcEru~rI_2q)7A!W$({E%~XAt-;N)+ali4S z#~VB*Jd0^Vd2(#5wro5CxFOKIYo$@qj)PRxjPn56DWFjHflnGTTzUHIkz<}c#x#_Jn=~%^D!#Oo*OPI=%%mf2^H;=ra!^&bw!M`SUj{?F*@ zn_}E-tTP@KEr4%VLR=tjC_)hn0znq}PUji}%= z>@SiIJqc{yv1>dDw5%Z{`>s)ROa-iZIeV@iwQ8#lo-d_S!Y~M4X~4Il@!4i@JD#8& z9pQPo`M7DPO{fJ9bY+!B_KImL7&b}Vaxc3wgSFPEEn&zyu5%r~6R_Xay&vWTAs_S3 zDnIfs_tnuBtJ{^8CFtg1GtD-ta1v4c$f^;tS0;T2^S+zawsNYfsl=_TlZc=vzE*C; z&hFQ|M{~ismYW{6Xt7K$SL#ov@DBG^1m( z)3~|0nFnOh2Ioi`1LTZ1v=vq111*xBhd|j&_y(L5Jci~|a~xV{{qP8%b{D<p4&$jXER|_F5^J1phjQ@~`;^|-89IvP~Rl(bE7Hd@j%8ldT8hryD zu%YyOPzG7LO%;#KsHKPuFj=W#+au4LmxC?HpJ)kx(3C{PJ%UA~3JG$1W7K|hI%u(4I~3%A@$R4*5epxfkup&UT{CW8 zjef_jd6uSh4V6QeASksn@Bb zW&1Og2SOnEaDH?O)~_y0dl0&wpYAn1uI+$s>du6+7tu%?6mw%sA;8i&MP0+sfKt; zDhI9zR-3O39f_#wz(=*~w)YT?v+ec) zIUT989}c?5gmd!KQRCm1Llgt9op^0z{eeNrE4&}xNiiZ)t%^FF8zoC);G7rpPtBkj zol%#pEL@0KV-Ju7k!2gD8hDm?SV-CECpo# zu;ZzJ%4nh!?9dt*?R3ySCq@&_eIYiWY6Ir5iMEzTJ*`re2(DncVdYIF*dX1%9=!D( zwzL@$-{XbYnKZR%@=2HpJaWx&?WkOvKNu|R1$d7S75Y}hm1>RL>=MgSylnA|xp}}q2Wc|{$Ct_5e4nKdi*5LJOh-W{*VMjS`tcnm+SCt-6yNrmH|Ci8WG~4=fiB1tbvSc`zA&`&#L+=4&paWc0!Ey zBN_%872CYCJYXhs3a<_lnD$;!!IWEV5~Y))2#m(C0Wm;wRQy00@Zs8~Gd7+*-p~OS z6W}_I>XBgR#48B?n%7TBXIut#uQ}`9rIJ4nXdK?)+Y1C9B;4a>E-pv}!X7pCPJ;}N zwEOrz1h2b}D39>SJ8Nu(2aT+UjOdWft6nT71g3tt__mV{5JuQ6hcDu=sr=~l8Ew6E zdn#J_h5K$peMMQ~9A3_8lcKI>J5kVgaiXlRJts{F;cG7a?BHVUU|fh@rzq{Y?BPj8 z-LsEa%u!XzQBRNa5B+m`GTRTBr(v&#_xdx(9eu+kYcg-_s4Gk=m2YPP58sTzr#e|C zghl9;7b@nM8^1Ad7xp5IO-3o)XYUd+wczJlFO%ybCZoscBo!q}YK5lSD`)N6mVC8l zUCv{vGGu)owQ_tjp`HT2PF_TT#^j8v>o*nO0!<1V2WzI)O5>@{aSCIlL(HRU7U_~q zOm;#cI?WxqPh?B1B9m6QUx$G)A#Fkyz^s_>vt`oU9z&l`&rH_)C&{E2NAL}!t7mO8 z#x|Uha}35a(~xT?xp`%{F4t?|R8_Sz+0^K6XI=X_3LmRUgYZ3U&!N>9+c9Cj>Uk9G zl~zoN-`>iFO^WP=he3{GY{qdK?GN*iNxY0a0s!x{BZ$?r_#vMhcEKed>C~Q>-n6d6 zu#igZ)-J+H`pEM7DPWvQN}Be=jF$Z3-sKrb`~t$v!Vay_P(9q z?0?&$wl)~lVm~mh3I|+ygWGxP5kuY1!&?>{E3Dy>#Xx3P7gSS(<8*;3#mi>^pO~I+ z*|?pDkqKHUtp@bM@_~7im%&S&hA_BXR^Qx4xacW~kXIoQ`S!UX5fvh7*mBOgT|}cl zcX{XM5$yEJ(Wi)j9NZV|SbM`IY35QJt$okEjMwje5KcEDTh~NZ;3o-THtqJM*O_SH z7#j0tM%~H`Zs@4&*VYh(SQ-z&iT+C-NT9IhSxAZYek7ixQ&CBT8P?IR(-vGdPM37S zGkndeblT1`q%{?wx}yFwoV(6)>X7E@XsrS73Dqcjse1o(NxgOGoKsLM2-JGI{;Q|; zabswitd-fZo)G9(l{bjBcMjEOd2nIA?JjW1Y6pZrG-<7wv*$+MuWY4j#aF$cLaRFE zeu653Ph-4}2@6OcVo>1?m)m~SV40-uet)fUm&3AL8+UTy=`4xwA^Dt&Gj|vZk~8nu zpXBCT^#W*@##g60!Fa~Z*5j%ViS_Cq57r*R!k*LIws^q7=1Waun$=Am zE#&2Xa@K$LjRa&BI)ENE<4)5d2ToB|lTuFIxz%%$z{ffEIeISoqct@%#eVw&F0uo3 z0*H{I>Hc6NR)#FHJ4(q{r6sQei$?>eX9XMk!`u6;*{=!r94lRh%K@^qbn<<6Li80e z1tJwbt&IeyG5-jLC9ag`M0+X0k@<|HHs9m>-NzjMOnt=LZ)QP{$>F2~`SdfD>J98s zMBH}SL|jU^K$CYGJ^Vw`OHf{(+0S!8KDH({8N_ zUJuv22Ql$lZ!wXTekp5OT|fj}IrQK(z3Pu-+4lQue6SiDkmUNLR1-oB*PQ#?7-;!OEf;UZnfL_ftfICP2VQUM7;5XLq#SLaY? zdsB*Unts#koOo*dpv;bgkrsBX2)U+1EKlp&;RarBK&5}xjAaZZ zDjo577;SYxSSj*WUhI|?Udk6{HadP$^x>@Q(7aWcsv^XPo3xL9ID^o@V(DAs*Hf0) zZCje(S%6ObG>gZnYF{JPo|$Sld+VH5az1kKP800ZKTEIE{ER`WqSmBM8hbX8$Ol$&vSnllTpHZh5Dn?9ssv~5JlEqq0NzL=vnp_WwaGu|b+ zp)CVzd9|z&OSM-eyO6mW`#@ym=1rI5H`No&%yhcCNh85~sdbrKj%(%UWGst-gt<59 zJnbGPpM9aS*|~3C2S53+aH&;LuLg8y-9?3KYAOAq&*Bm_$Q)?D+Q1?(xFCiIcFw3h zm`}Og({(g9yLVQXZ+To^=Via_!qdtbV*^2}9)-7{&zMX)3S`x=haTs+jo(=^qWi+> zrU~5c+Z{bFz8H6vily}RlJgr%{p|aF$A=rZlqyEZc&@ae0BJAp&*HVZeVA4LiRaSs z$0y25Gt5-i(wY`WFMvu0s!+kr>|F;bjM21%0Kro}pOa+AqCT7ewY;rzi-J2)0>(a` zU>}w&I{-_7#rx@vi^sszTivQ}HrrcPr5$ntw`lmHRf-wmRgyO+%98;~hdvsP7{M$dNXc826-tW=?g zeDhmygog>adEUmX*Cy1~Zlb9_cubW}S!M5zaJi+t>v*qVhpwN%#~=p{h;lg;ecaIe zNWICsO9B(%`<8u>8_BEMk)g`(YSBiFEL)kHnIE33edeJIk_Ps@8oija7Mo#&@nUm3X}Wr38mE zcZCs43NBWN!~5-!ZI4u5OyF7$GaWnL57P+{rRX3hbpY>x}nRZ7|CsT9R^+TyL&OTA0^cJW^B*sh`_f?mtfRWA}x! zXL(j!Dh)`g8EY(o2o&rUF@Z}qcekcRk?jo)n}I{SWJy%IssC8_v37LQ^XB#|2ABl7>qpK+~tj5WlB1U8_#=5t&M3rTx2;oA?U2S-)IVSjapvBD4+J`s^2&iqHLId)PsO zhLthYXu_FuCbyQh?%Ww@~p?^(jw0jtg z@YT6x_O_G4cg}Sje~6haQT(DZ#20UKsHz>_>|SksZ_`r1LplM}RMr zPp|auo5#xSsWfd59d`=~%omNYWz)ZSj#NnY*g={+`phHbaQs#b$vQO#+R8X`eNwk)@`W`31S^ofc4+qHMTN z!azvj28(+mR|BUBT$6({)DGT6X*F{q@k3R6|7`DGbG8@m`y_5pjQGyBF4}qn;u7oe znREbpne%9;iMej5oj?*UhI+fWZ9a2EKsm3RST!Qw^m>*ejrdthXlgfQ3`Hf}IM^>> zGZF@V{(Ty0+(?;_?Jgi2*gIj2D8~iC^MY3AkkFkZvvrN^@ygoVTI*ddQ%SVF>D$_6 zPF?C9v`a^_aIeR;eCeIuwTc>@k+9I@^67lv3y6mYZSur7_=^6y6yb5PgZFsl62u2m zqUz}7oe1%;hQkJ2~XS^RVfJ<4+^xZnQi z(Jj@n!=dZFJkpw7K0C3LzfyPelu-}D$z!Sa#W}?5P~={BG|M-93}2jY>!W6z0gYzb zGk!!@YEX9zSHGBnp2hIa(aYZH)E*Lx1K6g=7rd&qS^IwFIT2_>4hDRUsE+4$Mjr7` zu5uJL6<}(r@1MZlDq}auya36sGemicCd+~baZ)2=)itkX9d2iA1Wd{LlHf^wb~U&C zOgrwea`SqHj7HRC)82Yd$7GmCeOwtysUY zOBL~&c#r$*fqX=--ws!{GyR<-B3e9om@hXXf9K;tL(!-hD75@ci8io=2DbiU*oJeh z55$h4a%fiFsV&kl1$(T&rI5bU(r;;z2%q!2?{!Eh_VN)NarSBQ`#NlP^{KyhcdqU$ z3+MK8ME1Qe2?FLek0B*J)5lv7iCEd@_@le7Z&x$H%lj$y%K;f7hrN&Vrur#H{QR}K zp=yG!Cq0i`nfu~~>2qoe!bWt>b}(&{U2lBGRnwT+JWf3e`x{R=C=#{Xw{}7F>9f$- zZ)KZB6dh|xWiE4i(Ef3o@#0&FQZKP3~D08naS#+S8g!>7vGvGu4dM=%;`h*8|6<>dX(uEHTwz{6`9PRAgiC^ z{=i%fhLS`^$}iIWUicG|RB1p$8VZNH%CP^8ZhztMnAGy|Bm{XTxQqQKqu=XT7Lf8c zrr!r$rG8V>PZnxBl&9ik(J!Qb)67qnr|PbgI#l_sV94oDvMkdwv3ayzaQUqtKl2hy zt`A8aviz)3*!`2ye__|8F*;9zSA}Zy|6=qT^TE@UXRREv+@wz4Wxl3Tnx)gM4(yZyYzrOdeNQycOU!2wx4?|uhp&BwuVGA#6dLmQj6rgikr6c z&v7f6uULEjtG7{IBw+rtIBBydXQCSYJ(E~&K19uu@&Mh%MCym3E(3Z>mQsB+n@bxn zZkvkG{*HhAsqCm!62cfI6`y@sk0SLv>*H{@ZZ~v+W>n3!f-iFaHH`mM|JFhlvl()9 zW=QQ%I=y%5g(NG(*fo_EdD#97JuhZw+!4QU?VH=N@ly5L4LUt*{7bQKoPzE~_02Tg z)MDg*Qz*kPYSZED)bqAZ#*GB=`+tU#QVN~0vAG4&OFI%OE3tjpH(lH7l2`Q`Pzs^< zkV*da{|Qiv``0G?muBCKljg}`$9>2{Ol;f>27%YOO{|r(PVggZg?o<&h`dR&CLP5t zJ)+ueehrNtBe%TS0N3@fb0d=_wEy=&D5j?1*ZAWM0XE2Wy?{xxyF3yS{+}Y-r6wpU zQ(2Z9@7R0#1;eZrotpGH{fZ+>lL6CqGj&n#%(;c+ezUZ{tdEI@Wa6LiKPS|ZGJ$rP z?ZwUuQI3;_K5EdU>E6e6{ljt>+B;X$fP!6T1Rj?-n^|Op)+w4XWQbI|+4vUy2Pp8A zl&L>aRUi=PO#R94IJ%`3I!qUrmpIW6i_aa|*HTG)KGE~&i=(iCtDV8-ZTh|BwC+-Y za-nUd6dd(7gBwyQz9MJD#?LbQWQ#T7|;@vc2>HPNHG#77kjhWtDD~<88KinPKk9a zt5v=A=d;?Kr@zn9zvk`BGxpzzUDrC7O#VJpJ0o+^GnFQCa%oLnqw>@H80w{(6g#U7 z$Gx_z%zPoFW3>m@Jjv{bKK0(}csTeH^Tu`qd-8GOQA|bq(e12_4E^VMPS`nv z7P~na!ki2<+4q4vt17v_;@@AZ+G8-uShk3VAN=>Hp4Ze2H``#p+iBBoFD)p;Ih1a| z9bnJm9sEDL>@0T1#6F3iF1B#7q$(=vfq+=MdXejijAC~DJNsA&Z^ysKoX?(ybU)}h z6YF1j+~m}2!O_I=8clVT}}&qr0Qxj|Lm;(sc83s#2c5S!=)u9D6j84mi@k@)%Uk zS+4(@`oEUMdn9RS(?nM2we0VP5eV9Q2tT zlcysnb>wpG9P{YW^$;I572~?>Mc}wWv0(npK zn}u$bc~*9>55G$V*-W0OBF$^((;W zOv=?ZbymeZz9U~ALbudv>L))oaEJ;c%|T|O-*rqg%zka>{8y;LbR<@a8U8g{9|*h{ z1kZotQ#p$DA2zSbNlbN^wI$v4F>gb@p3P!NttQ_JzznKy_XuBVthgalUQzHW!*1%~ z-A-Cz=@>l5Gi@4~2J(_MmibmPzBX1KU8t`9E}* z>02V{zMr@<{Z#b7R`64NOp>HU>rCPD^`CrxuVZ_bPhz>aRNd-NlJElEuZ-jCBsvFhWy zz8C(Wf)nB-73>U;Oj=6-;c6Z^@@3oTzb(0GP^pn@uJtrH* z^;yH}&6oQLO%|1GQh_guii@=z99~w~G`3l+eEEHg_LnJTUX^g2S7Vp;dB7!i?1gTd zU6H|{z{%0kC-b~snk#H(*Dm!*>!rQXF!A}kFaG@FWo6&zk10DqI*O)JQp1?mtuYT& zcNVzyVp&<4RPgAjRp*noHc+Wz8@RvEw|Vu+8enOtghxErHG?nNz%LKBr2M-0TxKIK zs3J&~t*VueqjByYwvMr;A#y0n}^axfOeWjH*{`;2_0HE+yA|mP_!j(Y{OZ z@QtKa!kz*l+TxC$UaF!uA%l(+F#5*U(2zgP_C9HwZ%F+QT}QE=Z(3UBEb_IqY+Vr* zuYQ5!l!nMV?qoG=jn_yxMoC(= zzP*Q7+<244-VltlEbqCSB+!*ES-8H2@__q4k#Zd*W54zmak!tu=(O}^4CPN?%j*FZ zTj|C7ZDg+@GH}5Z9eIjc2fVYl_q@B(n)Q+And{;M-9}2DAAYHcz1&NoQST~YEPbk0 z_eDi3jW=Xt#)2ne{Q`bSzl!#N`omiXjio)S2oN{#7U!4ff6iqYbv^|J&0BQ50{Y5Z zw9OkGRz-Dnbu=zp%8S>NJf(17g?zq$b5J;b#ru6DZm0e+b%z{yj(;=a8r*j8{VO8W zcSQ{E*q#RM9@W)y7hLFkoNN|~^=@78R`C1!Vk{ND(KP0CCjK1q3jAwCrw|qplfb$`;uOsve)a~0i^x?Dxuz}|#;APwbpZ+_#6M!#nk-cfotae#^N0S^b*QmX$VlsSP8_Y` z+mPC8{=1`83@D0cDs0~G$NwrkbrFLoA`DNF@vPmd!dg|Lc@*vydD*S1U7DA zEMAp((|>c1n60|>T65Vp+>`q_$pf715wU$yIuu2=veP8c*_W~NQfdD>#43mOp~Hn> zhfXH%Tf84@I=Q^K?;03fF)=Z@z@=*v5mlt8=`+mTsYw{UWbu998M;J^wvaf)#D;{; zM;`TGR>AN3x=gl8OylTPJ-;&TR{?r&-n_Zph(Pc+W^AN$ob#w~y>YOQ2+^WyZll4m z5u=92Zoqg$7S%KEEjD%NeF*=j54OBv-`StjA4EmZW-lsk{=FFYCeLJ6gIa+F`MyzK z`4dg_>I!BLt9T9LudHl~0HlW<-E72sM~5BkHk>tDCuV*srV%X74oz(B4$4t-du1Jg zO+dP=Md=yn1guE{JGm~XM(h>sCCwL7e7HZ$_}=-<`IRxc8g_YZ(N*OUC*(Rk)N{G# zHG2QWg_s;UPbmxAsGk0u=KkdqL_)6!zRP0YZt#wO*j>+K7%aSh(&Y(*_iN|nh`Gdw zmIHlERuC*h(}duUkzd;vgBh@f<5NsxkN+9fFzH1VR1I|RAYT|+UKJL0u4H$yQPtSr z9PeCnT+O>ylr-oz+2r=j6%1EZBDADj^BH4F>wYOq!)tc*8Zcpe_pZYj?${0ldKBuW za@?k}qe@Enmgf3Ev%^sAD3=9V@oV~XVF)=fX{T>|%q<5`Ag7Vr!7DlYiytO^D?UGI zbLb%E-dhZ0j`R>m9O5s6q*^gyk948c)0I~76;_D$1Ccfm)-s6=5@)>~^Q`jzd%-Lk zjuAtnV%Otvc8);Y7sW8X*q=K#c*9`Qoh+0T)1&0EhErhphhIBkr54KpSWqmrC_hk`P);7?JPsdO zyIMxWC0)WRSPP19;uV;BKHLur#$eaP5;$~524K!hW5B1F7wWJ%0}H5M#^GU_{xuU@ z!Z_z}YlysuskZLbp}k3IXWM&6&8F{kgpUu(&>2t8l`qf%xKjO(Tb5kpdtNSdK8;^e zcKbqsU*Ey^2Vp6Z@$j7u!nz55{)0v0a9Lq?#GC@E`F1?lnQzxtB0*w-P+{kq?(g_u zycVNqZ0d z?j3D!ujy1X>tAap-Eef5*AnHRBP@;#R4kPoZmNRYY*yYLhZ%;n4He!35JL7hoJGO2 zFeJ@W1w~}EUP)>DHBlh4X)CH{UBMUQ=}&!FXXSG3nV@t>k4{5Bd^7CpOyjNV^;Xj) zRo*c*eS`ziP^@q<3DV|36*YswNj3q<8~ATS37jpMIp&bAqRrahpdF~_9Y675u#Y0pvJY^B3^nxx$|#D*cIWwA#5BRk`P#XNDyf=aoU!j@x6X@awf^tl}2#;(+Rn zswC|7Y?cuP%@?$}eQY~m_^tCRp)&g0+ovmRHrl6%n2wQyo&Z!&L$&`?P&~b&!Z%JG zW#~a^?=Ge!{Yqumx?MQ`L&Xo{@{K}A7+pI@_^zDM5Y^QKat&(q2-I179^9nXNm;8q zA_I9FuDDh;KCDiT2-(ZneqTG#rNKD2A{}Wju~PJ}eRRR;uWwZ_sOmICmP0sC`0iF@ zEMjj!z5?qXpk;GEpfD6`S52)Xmj0Y0VvDy?CqdEJWHxCai~(y~<~8_=9f(T_m45Py zl^ZT%ASC?E`%64&WKXKEHq6+->6~CsWc(|twPw8X_NT=c`vmn~YNo}d%q+o+?BU?S zav&%~VGFfTWJ9=tCtc|7>aX@FY_V;(to6?sNp1@xWO`PL_QP7|^>Rkl@s_Z!_QZo? zX;P%U!NxA<_Zp{Lj$S0CYpQu7jKhP8x}sqg{jvY+29BxpB!FQYB0tBqJ&T!`!Q&}G z(D!~r8_84HZ&v7vjay|&LtZmYLW{m~OJv;Sw&ZRH&CMYR8@Sw8wd@Udw8PaQP@`6s z!n#5er?^SEJ#*tdKJeaNV7RETNRoc;(e<-QQ$2NO|REx!^>1Ov6;ycDG;-)7$50(uKPIy_E8`5(q#st2tjN8*zjj0X4*77E#liiNsHz zQ0#DjWR-NQr?mK|&;JT;HSP6~P1gPaJ)b|sTCTYDPdFCH-H`5O;JQ-7fSE4UZ;dT2 zG7f0826w#NcPxAUDzFH0@itx3XmT>3autop^V>v?O|^x`$V*~m0M|JiL}o6OtC)i! z8A{uSneFR3atn1Hg4(Eebd!gl{4Ks4&Z@xAxZ4({4S`FdGe@kYsq07}dT{ zJ*tihMrM6hq0W%K5QmtRlfX`$9DXyKpfXv)Vi{U#($6F7>iO(_8wEn&TA7wChk}Ry zEIVyWBs)?rbZh04;oHJHw{ldBVF_zjnZ-FSh6(N`hH=t5Z5ZWsf4(aHE0QP#c+%ZO#< zbcYnQe1_$jk94k)MO(_u?;*k2PNE)a-sC4NdZYj8^>3J|srF8NVA56GV=I@O`ZTQU z+6sRN55V3xg)H~xDhEF%PL59wJ$M+Asg^yPvmHO}oob|~qWNP7FbiAiVo9_6Klpm@ zc&gj~aXhD^jK(oSStr7gos1|>g9?e1Y$7X!jLJSu*(#%CWHp@PAVfyPQJImw%HCyf z=lgseboaU6_wV&eZ`#i zy(?dVy2O0kbg$BJOUsltui;qT?UJ+Z>7t(Ek~6iPrV&9?t~)Q}pG}1*zxTabbD?$U z4tg$72&Zg#(1U=_*n)!BV-!K(ZvDYqY|64T5^xK(X0xxNpx_qT5&7Xb!cIa+Hf8)_U z>G4J0ikGvl3U)n#5{07uKC_}EWxE1t^8N$KPrKq?WPVLQ-m*E%O5MER*n#PXjwjsR z*@fQr{&;`0!Ftr%bS`V8J-51l>hAVNlk~kMK~VhY=C=^pEEo8Lf=@+iq^!`u!LFTl2n;%Ud}#VxHgpzslOruh`Fr&F5#c zH4*<%@If2p$H$d(sMo{&9aXV)$*{tX$N*Yy9n(VU^HsLm+eAIWj zp%goF?(&O7;kY#2SC?PZIrIEGX!*dG(2jdk_8?N3((8EG^r-Ue!s$!wQ}2V%e($-T zy2bhXXWNhYisl5;E5qEFk(h-Cb?zfgJ12sNq-LhDRn+dO1r{bAx^uN7pG{&EZe}3~`;K5?9zF9>fg2+$t+)hMg=x^bMbYH_xG3|Do9eE7G_IE^0w3l)O#7d3+a=5pAEkOBP|jEYk1|Aool ziAaS&X-CU{rN=SuaFPo`egA=;4JZ?MQlhW)*^!w4I>`rd;E9n>M*m8-P`{Q0B0Mp7 z)`sOj*2Oo$Np8nRGycin|ApFX+Q1X+@eB_CRqLQDfC@ndi_3o?eFKdIJiqw=L(CIf z7I);~@r2-EU36QO>fcE855iLp^Ygjveoak;{m`e`M?Sb{W5e-Zz{?}R>qNQy*CUVD zbOCs4-mP2zr)CKd(>(*=orbPl{(I|ndQ~I9k0d?)?`Q6impwF%tN!aXN&psR5$C;s z|Cawi3ib)Yi7(3E{<{$c+4i7F*g$jSH_26S60yI(9p{}lf`si`rQv+@#z#ut9N_P;@UEJ3aNeXLa8YE5^}tUG_lQ=TK<2oMB8IxrD$X)*+jWP6V8m$%ydd8}}@ zBP_Ik1NF}}vlS|pxxIPKl&)@V&_slx7u(>%Sd%?XhD zBeJdS@e)=?S6edSv3=8sTxN$DI#UzWSPy`~a zVj%E>Lla^bKLx^P+#y?7=|+DhK+A&Kb?lNTht=kHQ*Yi}G#d1#Y`b^+p3Auf^H~1V-{-laa<6SQ___2^k~lq#;Wj_FZW*W<)z#|m)3~R=9V&# ze&SoD-%xbk#M4-psJ9~{F7DUTWL0F2L+e*Nir3LdaYLRA6T9XUX2drKgJDBE6x@o!#lNq%<{HZp7R}%|8dZNa&9=nq!Q_o|l4Jv}SbN~o>w*zv3k{Si{ z2R4p^(LTp4G?@*xx3iymUgSks zLK7(W^LuX_@Fad1bHbjxGC6|EpX1ns!;*?k-M+=UF#H^SX^A}U?$P}Jnah_N zvu6KqdkUY!hue~q%8unT+=`Y^$eI{Zwr=p;IjZ#Hk@-SO`SgxMcjN5Js*L83Z=4=d zj2ory(Xpe(uPJD!G%+2&)7+)6f@wK3tMr|x&^NnBn~JvaDvhvk)iv0dX%sUvGe4nc zOl(wp{Z}KPUoQGXLqx4%?qFdT{pV?HTLaz(zOE~+ z75f0WpifyFjD@@B2VH(4$9;qFTOkV=TdFkpS)=LvRmQZ@nGG9uwu|u3ZpmW?gA1gI zmWyY6g(z1(VNA~JhvDz&U*AT!x5fiKa{0lShF~Qoso>&V+=Yb8kwd3vzy5yYV)aL3 z5gK+w52M>bAp>9ux=*+83C^GP<3G+_2*3Vr7uIF-p6QqQntr1DR*e2}cS<}FTQI9( z!-SB?1@%}U^*#G+$%1<%NBcbAV%|o}P+P|Rc&ku#wq`bGnLm4hRKUZ0q8>9wh=}?# z(rnGKNsTrh@#D}p&pF|0vXS;PU5HA*zSFrsQwRg}l5;)}M|OHCuZj-e-UJ${tC7*0 zOg>`*2l(lR%n6(Ng`9S>az{YpWCSCO7V-7l&kGya(16xA*buHmwMuggHj+UJ(dqZ1 z8u#lB^3v7yA8%BXP?l-=#}%w$hI1Jl33s7RkVm;Uo>bJS7*jZz8hy0q3+>_oTitl$ z0Rvn0-~aShVbWlZ`Sh17rU=bg(MttAwq~WuEW!+xUH5m>*=(p&mha=?7r&*Wz2h&X zSq;{Ga*02YfXo`k(6O@es{<7jDY`AjBBP`3je4H3f9@v&Y0XtY1c?VvQs08~Puy^r zgzLIHSljYu;`h&wiRlJ(A^KaEw*T1xONNqtY177DcSH-HV4T}rnmxSdk$Jce!_R`a z0uj;oq~8br=7IsFYcfRfJaW6m{0LS{tD;IW;2vj>peW4^i+4=_cnjv|5I~gl$B?#J zx-36mEsf3gLO3?7DO_BKvV3ZF{9mOfydC?A0Xe6+pA9}NuSmc=`C6u$1XdC_D6c^4Qo=0_(!ilW z8zcSrx0{ZR9&xIO{P7P#5@f(y+XbHvmPORHHgnBu*RcdTK=L7foT+Yn!KTmI1D+pZ z58F8O_)giR_;cptiuqBMBu?_y4K9BtHXKzvE6TI_z{jHnunDQZjcK_8YQ-bJ^cMBJ7*by{Z z65hJ>D~LtQIy}y*IP#_>;=GA)!PQ@PJ_;nzoW1c}9D@*aP09~KUv9-@sQ)Hpvv2S^ z2GM7>Ki=1*<68IDo%=4HSdQrO3N7+&`}I`5|HsT<^Y+5Lr0sXCka*=ATgJ^@Gx8SS zE{C3fUF1|hl$Q~J)b0*h&cckwxmmYNrmF|0_`VI3BInhJVAg#N7OANwX*I)tj zaeK@_hsfT&J)WJ5ac_p3t>YsgS-j~bIcFEm)vHE-Kjr>^Lzf>XX}Gm~-ru0rbsBj$ zSv7f)GpQXn%VB}mOIqHar?gI3KfDECX%~yRGJ>SLs4)%RO&#ez8!KYuPV@N8T^OuS zi$b{L-p8x7xqq2p{0P!!RcQT{n;M&CXW)H&ik`Ss9ca*ZGa^;RD(3#)VLt3SMkF=m zOOI{(M}0OR>a&HV*9TFbY;4%iyRTHrG=3gyFX)Z)91DM;^DHOBX(TS>^+VF4{afjE z++cN)d0!v&C{b0_2fp83ff5E;r!}9*Ymem*zs@ka`QR@-g?IyC-pgXskYC2JVe6hP zj1x_ZO^PVXzYIkr3a9R8+cj3zRR@#*jxF&vfQ%!PiLwYX*imC+55{-xNh1;;21*n) zd2|4`KiR@_P+mOFTfHNB(fj-T{$8Us34v|svLAv9ioD-*kor3*fdpwSpOyLkD}9fw zzxGC%^61R6jm6w+G`o~%d;q~WG6w{qoN-UilJEh3>K8k9U{M3R;@ehlD0QcmBRqc< zHY$`^gdP&4rKP2*j*5!v&nnEzYkKy&%H$C!dL5xS-On6>&z@Bl{yG0E#H$?kx}1%? z+Up-(()QLxpaaDPp8V@>#fzUY)^AC-VeR&3U%{(PR*w=_YSsHoa5m8Vq64=|vHC_N z0%9C%g5eV(-au>4(#6uO&^vej!5A6?IfV@jNXSdL#$K~UYB``0TlVQ5OK+1!sKbk4~#*eW`4ws9JEu@%)aW$0o?>q!OKKi$He61V+w9VJx`oxMw$~Zj~li9iurhV zUP$#|F?-%sw82dzMm0DpZM%w74%z{=ovKEfNKBY*MdSH?8% zKo^yDMgtvi1PLMeIO?fF$W-(Cq5E6P%HpVs3DfiQezPYF_y5(#`};ZM3qqAR9wUC? zwZkRbLYnpQCN?Y%Hr%L-Tsa~A$3)ik17HZ&{q<|Z7}bTsmuw5N-EqTWE1p!&oKbK( zzf-PejUy1Y_+beK(8Na}dn-c2Og~TE!}(w8y*b9^hc6o69ugay-3$TL!=?}_4^d~x z(0~oAWw`+6?7fRV_z?Ofhsv>8I(I7A$$-U%z+c6J{By6WraD}C+&AY#<&C{ySYiwE3u3gT$>WRxtM=^y`C^uQW_~e@?4T_U7 zIg8wN}+S4dBovV-;S?_92m4@F2 z0ufz%M30r6h%*;9EU_nLb~{d?=`dJ4{D}+AO1|QsCU$%1_t2MuVmc@jviT6|AyO_mer>@Pa9+)it zl*=x83L@xkw2|8NCt;2B7;IRv-%gA9vL-tCT=TMIZ0z!mtTOX^T_+CN+2p+ERiR5( zA-NX%zvtXT1E3?2;dN0A7W%jA%-~y1DBh!(nex>{EUl z)+U&-(A*}S`p$I}&h&wn?U!7nkMxC~`}Mag7v9=;1+OhS?8M2nHa39{#`1lzhyMeB zu`$I4@C{nJ23U-8h7JbVYKt!Hi281@iXCLUhB5wv{Sec4U--NQhbbj3_*ginOO=vn zZhpH^7hular698obV&jLeRIO@2|!iHe!`KQE-$-$O+3U*Uz;d?M|^Df!1~AygctO< zpi^Sz3+ezc6N9Ko$b8OMrcIYB#f1Z2(OX?zL&FBb7i66HMFoV8&d4~G3#I!G5j^@# zJ+{ByUsT5#&clLGlDe{&NvwEFTwHHK)VMDT#e7 zxITX1EdByf$3#d75PlsM*r~g-R7$QlUX|wUf#yXDM&~Cgw$p$uQ zX!{z65UNJYj3m#N73%KpUcqBGAouCAHpcMDD7$p2=qeo%xUR4@9dPbFI(voZhhCX` zW9-$SH+Bmua2{RS#2@!fBH2r$a8LBfyD_Ab*7sD>jT8=qG!4+wpxPOYoa<%U9_MMMspyVc5i8WuB!N z{2+6_PCtJL&VcnK;PQ@1iN+pa8zzZGvIuEb@GO4|#kZ-CR6LTdzs<)Oxw# zlxQEHE*%DU_(y4Oo52Uk;Be$DAs8p&UM6|tqMkEf#oc1T#g}3{rY%?)b#6)~uHaqf z;`92fPqmSWHalm=ZFU1%ZBgPWqGDZSgw7vP?A#p}8L6Zi(8LBvil>iMU2T3ee4{C| zT+mqItwx3HAHtsj$yB?S?F>Mh9YalvrdUdswfPbY@hP+<)Q8QL?<2g&$Tx+^FycWQO<8K&58N&+pz9Q!uDm>*Snhl zh^wrFQ!hD0-V~7Q8MGChu8%^iie-$fSbY15dsv#7AZ?+yaGVwM(NTXlDeh^%lDYbz z{T_UR8OSKN8B5x(w}s~z1^+xT)@HlcXT-~ zvE)n4gRdVTf|MXBi!Dk8bs)(M!bm_pm6XN2v!cgDKg6?oncqWIXQp(R)M{jUs5ZSc zk8ZwDzONKPjVQYohvAsH2%u1nS&M@c*RsCh7X~hwK+#PRF5#j97u>=##Dl~(ojGi; z{mBKUaY-ady1xrQ1RNVy;P;^k&!o%2NoSG;;%$;3j;X($VYxAYPkZUpzS8E4+Di() zD_%Q7$FnYJZ!r|GP=w7MgA~fRWU|A@D{RQV`Rn@<(TO&!w8=>U7pMJ(y~*yrnfbrZ zu`dnvyA`wZ?)b>2au9^8qz@EBOzVOBfa|w(fGF0JaOfX0>VEhflv}tcPpF7^J>4`h zb3rO6*H~VC@k});(7kqa+^hb~te90%{czgM8-%pz1{Od-b$JH}#Aj{LObO5Tt`RJN zNgjF{-_5borlj6}ehz;s@Jws=gBj@t`2S(&-OAh%#UZ0q|wXnA~Z$bC%8S%Epy!8#a$}2CMR%`r0%G zJ-463dCNnMNx`^~Qs%P9GP>p0_JEWu0)9qg6u^d|Qw4!Hv_1iFHyXj@snChWh{>i9 zVq|1fYzBewI)ML}D^pVlI!5V?zE{~kxlLu^prZ*Z?qvg~mQOwG9?+jONtSM_9pA5f z^AK>^I+xq`x+GZ6sB?@yHW-<2g_^eXAHVoAYImHxb7FwLqqBZ5 zr~|9vH1T>?D)yR;5LJ*r|2_mj-arsVS8$Z`1#LQvNYL)Es@IQ)>`4mu2Z^K?p(#8M zjw1+P-Pi(%#%XwL>MpX6EZYgj6Sfy@yIHn*nJL6&L((J{t_@O;0OPToAP@FOY_TvK zXNVQ!N}){=PD?H=G$QZsy*7X_BXwE451?$m^t(Soi)6fkyc(mLPT7gF2sTQplK3(( z-P($hn_xkyV~2T>X=WmXY_o@K_j42mPi)f+^m-fYu;)`g^#sVWS{Xpp_wVIgVUr## zMun7b#u{q7TlnsHDaX)wsfPhNsGsehbD?EH99!F}!l@u7uoJSIqS(H8*c$P#!iYey z56D$6pLa(UFxEz^7A>Y*CMo9sNJcjeA^aMQ8L=Rg?mi)_hN+P56Ojb2NL5P!Uo!;B z9=7NIS`d-H@&ox~WWgX>wZ~$*ccluGYl$R-QiOe|$k{-s1n!p2by@W%2F_uvg6gx# z=HzI7i^2)Jpg+hy3b@&F^U?{J_Srb$Q?t37M&+(Ffq(QWrpW}P%=4%VQZ@sXb!IZ`Gq*wVfYo}Rrc?CwL~UtA|RUA>z7 z8TIoZ6A>ZcQG&?pe5=*Lh%$+mid5edd=pltrAzMo#F161W>T?B$lGF?j;ybTH{+&(mA+kaHkBqDd~)53LI+?Z4ehT-SMFL<&W{1EhGSop5L_6- z((k(MApXUCJ_7D%{e}H@_HTmEuTlyDkqdm)Cg&sEg_p^C$1^lRD@LNM0=cTkMX7kDqKm=(aPw*eGJ0vYc>oX9$wU4%fLqvK^6YFMwb#?zkb`%z=O&bN4-w zR5{9Za-9TPaWG^OSsGm+%Z6?=OF;T>S8T(L;?IG{ohWfdf&ow*%B z&J>?nj-F?QRqz|@K@EU(^`P@`3|TGv$~F5MWB@z5f3#@OD~PL7^#iSC71YbF!_Nm6 zC?7t*60t%&Ys>%w*^EBes(ztU-u;}f)|P`m;f#KCsY?`eHhRLy98jq z9<8~%PGK@_tVc{)CEA>=19ZhpMt1SDB%g_%FO`}-I9T~2?su2r^K$SttR z`>rg#J1KWNUdJ`_J{xx(pT1D=0ff%0pb+kXdvR)yRz9!_p$%=`aK<>{URk!&gI2w= zeQ_;{3lJmR;*%mEdwRdD^ROCX=V7tM`T`;4Fp{{pO5ifTt5 z%##cQKHK|>e85Y;I(;HKn-aY`aoNE$X!d(SAEsV0;&VKRXzS3v?dsawCC}7>PnMC1Tr_$pdNvylMTi{mXQsX9~1yb z3Z%Ay8qMR)0$}Y^QkNExi1QvYE*$UuGR7+Sl#s56+B)uP5Ct@<|JqFuEJ0VMG!2Sn z2XKY~LE6sVV7%54h0b9It+Hx3OTL2&iSa;B#{*&bk#Wg{1Br%K!Q6yy0V?AmY8Gq_ zIMzg~HGkHFMbeNcH>Ar66gj6U#E&=Y0lKjYt|K zYgVsw2f6{--#be;1XSS;RUs&SCL_q%FS)hAvO(`Ct^hLI*MTB}yhkJFy{ z(6HiX(;twb4Bl*;qi6B*+gKjBbZ!$Dvu`#^{Q))+#5jN<_dSAiX~JZG^?4~qQ8ABK z3Wz@Gmq?+FoL3b|Vwb-@adC$-=@@A^@5cEam7e1j`?LPhY|;0@?Rl7(k`8-voJbH4 zI`(b&IZEF^UY+scMmNJOL&%F%?teWfJbXiM*RaC*+GXNDu|7wdAH-^`J7?E4t^J;` z4r$Rthgpx+qSW#`J337A)28Vei203rti`&O+gjV(W2bhfw_PCiP|g@>hBp+?s?umId%&t)7V$;awo4ELG0xc&OZA`oP=f-I zlfk-z{94t_Mptv1E^d0B1S_p7VziQ2f~4RMmJBqFcItE7g%fbgiEsO?fr^VIw+M(0&aO#7Sf*3qdxJ%Z)&+ zbg}wyut~D(%E>5=ZRGR|f7pV61XbJo`e3@&V?1k_X52yjmW<? z$`}G>H?ff=06jVb-zr^@h)cytzrN+q*9-ZvwAhxO7!AWwzX#y1tRcb%qUHw7*UK;i zr=3uVx|`T^P5Sw0mtHGv{rK0xo$JH_abqQ?@*GBjfD$yCwUo@8j0+hZ9X&@)NvvU2 zD*&{gaJZ}{0Ka7qCTj4l*A?v2*2lru91OXnE$la5ySt84x-ER5-02Eb;aHMe2@Z^0 z4y^jAyCcp^l!~Qa+qL!2#+QIj^|DA(0Y8pvWhJwoPmNV-B|FBnJ<55~@rQRcPJ%^K z`&N4%w$?ai)|&YB*quk-a{M9AzVfTt5h{xr!7;7`6P=NSk2>L$^%5??fnGWDWA}7K z6~kb0_mn-7{ve#DCrWn<1(-|eRNPN55iBKn$prlqBCx}mjQDK1U?#^2ytX}dj^?rP zGkGg=(*~N_Kl#d%7Ff{k!2yI+mUR1t?QsDS;f#a0M&Xc@GX1}zF;!SjN>wEuff7aN zX0Qk_=clX18`~IgDT?3SG*kphkadbz00R10AqfS{`~vT#{?s!-rv9r-acU{);Bm*Z zYiC(aDfBX*?1p*xK-BTdse6fftSX}D5Rs5qleF9R|Fhy2#7f`FmqiGvO`}4(x9RoA z%e?2f6knFKT!?yt_@1k);w=D9@>zEzI6r_Yw_1Snuu$BN?<$poe}9#>>aeyG!d#ZBKVDOLEioc>wo=w9`2Eph=j28Qb+Qbr7q2b>NIFs+RK{$Wm05^Z* zk{A4q7JYg>+M?qJeaQLA^S=zwm+cZ*=Bp_x*>T>uZslYwHB%^vcuiV)t9cAU2<_1J z6}e8YNn*;v^#@kNiDwTsNWYEbKL0Cst|c?4w!Efm+Hz4ePRQGYT_#aEt>x5gu1O9) z)^wtH=#EJx*zQgc7f+EsGvwgCU4Frs zy~_NMPl(~r5&afQcgVO$2P#}{Lira5CpX~j7ofOIQW15`OLMix<-928h88V=54GegfMS961z%@DB++D*$WG%Ic2%&E5)194xNexyx+zRBFTXK9aCDkL05aCZ zp;DCFkCy;}lKlVtRc&=O&5| zsax~a+y%1mer*9ZFUbRHZ3+lon|sWxd2@4~QSC^EfS#Q_L!(|IN2K_8n?U4?&l$rL z#hkwLoxy2Y%r=Gqu7h?zc$y_s)Xg8QE)YhCquay5oi3gw>q)ejvd}CR6=e8Omf$cq zvCnS4cz5bYx@^cKH|%>}hiKg!DE3ns)%l1g_zK;XNdhPL-1z%!lSFI=IJ%239ABFj z_c?nKqP=r-bFY>hT)OJ+ZY6iUvxwH(W-pc!qxL2@e#x=A_7PQNfpr}i!OzUG`8fGV zifYLW*!*wkv2LmrVAwzA84=)GP-H)&k&WBdo?q77>pM#>dxNEfvP>ute84#zZZxz5 zT5u$R>uC`bpai9-d_3z%bN4d_tZCNwgah7GdH2vqRmR=hm?+7nvQd2zHBk(&CsyMq zauF_nsy|4A?Xpp91k_#VkC6ndzJ6CX#ssPh8O!RYO;PfQ_yqX|AFdwX#Z-qU+AFzF z2N2ayRdu6K`g&XVQ`H)30IK%e_%d=zx>78}8x3PJqBcevo1{9uJyf{V*^v<{{h6Zl z@XcYfw|?rvQ3XxlHWTiH!z3cyLRL$_67pO%w9OP~LYt)nZ)fH|Y_ZwLtu@*r9 zY7<5`YRd$p&dfX|1~b#LNu0bg*t0*&Oe#d1BXFl>WD+e~dk@8|^yAOR(Oxz6`JX^>+EP+ zQ)L?XmHsHY3FYGGT}-QHKvEOD%1r@*kPCTlC#y~ZQ6)$MF?&=U*wz?DMAP-_x3d znsA#M)nCk;hIX#!KNGeBi_5ac-rPVip{-T_oTzoB?v&hgOA-0b4*%%mQrRYIBHKPw z6ecW{BI78Qs_oBLJp_U{t$5K?=3?w}HQGoSm=C%R0o9fmK(2UP zaFkaQavP!4mLTqvm)=zJp5ySOHtQ*g7aD=8`~xP##$l9K4>T8UrSwvgZ>CJHN^Hq< zS}&1a{Lk@M4}m}2Yf*!_zrhVYo&b?ZGd$34?hsDZJfBRG^14 zqgJ)`J^0KgCcZa?9xm&lPKqeMfhN!w&om zcU}X2%&-VIQ>z}8v=x!Im(LBw9jJaBPD!zHn{@7fLaE#_vYr8KLQn>p{|G4F3XfH}FCao#x< zLdx@BhT4Dm%fhk?yUx0gtdTqK=-etuK!&O@zmADSWX=#IPxAbEPp}e8BZNKrU3Q##VKY`<^RNtm;&S212*6!zq;H zQ}{;JujDamTJCr0-6H0wsKOCT&h3luxBL7imuXIyWM6xD)R{Q76nYRE(G=6}9_-mI z5@=33U4iwSO5!zccW-qV&u$XPU6POu+q9NOpV|v%Q6!fPx$@&!G5d#l9G;ZLPSe7! z9qD>^?TdcZ#ji{aWho73^;N4Ios>BGq-##Sn&RJlS83|$(8<#tXWVAnuT~$KOW1tq z`2yK-i995H`nsB1=VHgKmR|fc(fZskuYSet4ie5EzfTr_ir%>vtVlwgtx0ms07`U( zlKIL0eoy18?QWCPXLN@W^(vM;D(y^kmVffDT$uKy{Gz;4TKc(NVtGECcRu_;WuxzM zV|Amt!3znY@(UFy{l1@mU$6EwnGU)7E-4@S&P-LFN(Pvud-aM~`|@_o7R{ck_Fb9W zetV&yc(Gu+gNWr(&3RL`il7Hnb%xvGkR8n*4wI6w)c zk0xvr|6wS+c{^bnA~H^h$WT9Ls3jCVBos2e22Ag+-W!A0~CHMN3U*9>!*v4;^!YlsF0d|zOD8iBVLnj zov-t4b89pntV?Wg`9ev&VU@9_SpGD65YD08w9V>=-#QjA45g)odiC(vNBPc2$?X~% zHkr+z_c`s^Qr9<}lAavJulXercusHmgT&I2>F37}z4Z9N66bVrV9fiM=Xujf8B-Ut z_`HhRGv{J2F`+pY3H;d80>Fbn%hz@z_Y;7 z)|QvC((B$G0PdS!Qi;;867CbR<*Ag@G!q!jT{7j)U@!W8`&`B2pk9PqDx-}O)Fy3y z3Y`8Es{hGjNoo^jiz&B}wYl^zs?Zxo!nJ{>z=>yx|VL5m!B>(0)FhFo2_cGo(h zVfwXFk(cRn0qGou?3;%}mw1CrKCbaB8ax<5j{U}hS~p67sn36q%-v1+ z{Z8o#-|Gj5UrDJG<95&ARGzz8pX*hg`(5I?20HaIbUsHC;exs>SgT5pG?&T zf()=hLrEso^*_?(i4J5eZdUqDn74e`XykGVF)q9vFKWcz_Nrg*ZWa^+V|7~h+3EP# zKEt^_;O~v7NO*jceO5l9H-Fz!Z)?CR83wc<_&I9y4BkL=A>^SyTjJA}Ys+!P^D)KO zr$cCq?pI|z9ul|*$L&)3b-fzlyYuHxd(=L7iP(wyQg+jKUS&6O`@$%+cdKjGJR=$; zio!QSzGs;J(-yic?6$`$&Iu+)s`~0}lhJ)@+e**P`<^yJOk{RIeDUPfl`5kA%+C1n zotrntA6%R(Q&x)6XOk9DH@s3#sqM0Ux5mB{VBh5!yYLN!J7BJ=WZDX&+gD~OE1mV6 z9l!51U*nxh*nBSZ+9&>iot_T}4Yvtd*447j-r0+vDa$qQmU<=9;}+ZcbwjULSiEd3 znt4-?XozL@D5AQ6~94_3z|*?^CWH{{k03s9}s0z0tnZssSX^c81`ZQ(Y<7 zX_Qi3x2$GJi7=gym=BAeudoIxo;}xdQe6GtTU}gu*g;cszIgv<**`3U!3}d;Z1&SU>4(onX>Y~cM-&gz`7Iw|B-_3V zUvVri$)ilMLrQU~#V-Aoc|&wB4CElgch4n+T7BJG>?V9$ z#)p$;5A^%;`XD(5L~OpYAvNH+)f%vnh*?9$#iNbtWiIhz{1nfVNnd2a%{nbBsh~s; z23L?tFcwJ+K7#Nq@p)7AYeZVLuN038 z9{OzXruAAAtB|_SAT$EV7Df+B5zJ|+O|4x=pQhLo?pu|ArewN!HxH zDuy&s;OZE_4|IC7DB9>uqzXswu4AmUwS2Z!QCi|hzED)cC*q=>Pof7IE9NaWjF^7FgYCh^ToI`4U@H##Y7uUhel8V z#*B)}ur*l%BF6Eu6Wi%AdVFCWj>2NhqRsWG6=?Gujlzd|tbwF*io(XPo)0O#rET!8 zELEH@BdHN2$yK?Un}P;i&F|5BW4P<0%S7Kti|GyBH@J29WYmLz>oPF{hF4ai9&aSK z7Xi=w>9_*YeCJCoM$=W$nm1^8#J`9>VD;s^83}XNLmPtbnBj6&K}yt%CMbMT5%f<} zj2Rw+!0)r{fsVMdT%3{|+_JB58C>UAB(8pONL#8FOC+x`KoFAi*Z5Y$4joX;Cmf5? z<#U*RKHqMj%lbU8%z#eUDfMQ68hRuRNQU zmG*Jz=@mJb$1NrkzL%KlOjrF8C|fdE=mb4hV!>vdg>)}gNzWivNUM5D{nzd&@>LK1 zp`x-Er$>b-aK|R`wt~+@F{5!mFfsK&T&#I>wdG@1XMPA)FJSV6sB)qMSmYtC z@+K=wr*w03G5-BApqp0Mo4u3UM0<-{?hs3{5NYope^d-L|#8Yko&(R~pV{Y;;uEUcBC* zNE^`h=!5RO@>=>5k?j~@;LA)hJV=9DU@7$dMyr>TW3bv7bqVgk>1bgqeN+5Ox})qA z(}eE;oeff9gOr{qleVX zYCG~=_tiIDHePY;Z=C)%QEYbs?pS6csce_2Z6?$_(Fkx@xzeXiC*anjKASaPsdrfg z52vi$&<#B6a@hjy|-I#7n@b#X(Z5Wa~`Qwd<`8x%X5y?+&AICtP+`n=ht~J4&A| z`lD}A?4|-YoKFx3VnVpl2S1?u<%bSx{OSJK!)&vh6G+G1&1&3-h#H^MH$4SvBgB9@wBnARx@+q!+F)$bY>XbdQ9|v< zZH`^cls>*v@b1bZjjOFazN_u@s}cYSVSB^FW$<>fJ^_bGE+ujJqx1`YYA~WmCK5l1 zSY5j*ie7c?14?^KIpwll9S- za(ni4#p7s8D}8ZHmc<8&*qyq+USPG)>i{Hlb({F)rLN>W52Xu$zHpmhY zy5Kv~dApz0-}8c)$nQ@-t-g)$H>jpDm=kASrDo^6#Ispo>XuLNL{S(%Eb2DU1)YPV z6T`nr0HaL_L39#7O^9SAZ*#hK#Oy38P=$a)e_Ctb-B;>o`6#SEXRZLF9@`(7M9eLx zct673DLw7LGVH8;2waeE=pE&>Hkv}5H*e_Zn0^tPogx`&rY9iD2l+fbkDGI)jcf(R zzL+Y~2~255{)sc}WVSM1T0aiYg9Z~UVU$@``j|dnh2gW#qQ%AARUd|e#3+&W!v$YD zdkETdUbID)2?sgQ4>+N?=|p{gpAIy)b#Ws+7r&K~UOzm3I8PjNiloKHyHXvmY+7ou zL^LoD3b>HwI?*OhneFy4S`i5uHPq*q)CF{rR8@-+vl0(`_%>`dIK!b>@{^wMp7t?Y zDVyRhl_VUN``n(i(0kjzjZ5b^z9ll6y1h^GF`4xoAI~!{=~{{F7lDj+=&LSj$J+mQ zk>HqFU1HOHoyy{f@w~1x-uA4I%wO^z}0tjRWr|RY_0D^st&wnE4*hmPJl_&28)fr91 zFAx+q{ZvwZur(!w1-`a@lvoyQE%l8ORFvdiFXd`Llwp74aGfRCGnNy|UgX|e0p>9W z6yX0}kJ7T1<;b~iS~X05cuqK-15RNx4JwIUi^U<6i_5e$h2Hx3TTJHF|Hf@0_pq+Z{btri=p+(T-7+t*onWDZs)`T4BwMV>5CfxzuuT8z6uI6=1I8e_x3i8^(YO zN!j(M17aDfjnmyk$QgPt%U>NcO2IX-VzB8k7iN`>&p_n#UlTb`Ld=`ITT0 zOp43x;$v=s%LD0xm#hH_?Q(^b$RkDjy-w}7HZXOTCA&`*=F7Z%TlBk z`O25?I06rXLIx%+4v{@IcTWDr$&&7b(yk`x3f)@@M><8J0#Ai`trclAK!(^YaNA#@ z6XkPqbI+Grhz3|lZ}MGVcN>(oxcNwM)FR-{1-3@`w?ip(BV%KGLn4^%yVtm176h}^ zzw$5=2_@hfB9kW(rScZea)sS+RmS0t0CXm82h3idPH3qakTy$SM> zb{B)!*uCkGbCD5Vl9{OIE%78+a{!C*iToM7^T-JviM7c3HgMLCUDrMef+a~@EjK4z z1V+D~ zos{H=a9$W&nRy$$k#>elM3^RMjRT{B?<<9&CA|hTkzQ1O71BUGO`9n14mV)u2>})^ z3?i$`;tvD2(9*VQ(QgsJ@aEKRHw~2Dac3yX9q^)+kN0A6TKJXKr8l_q!fH-0;UGXf zVSf1&+GB^_QW)qPkzz=@W1WbAPSP>=HG==SSYT#D$i}%t$FG{U{eVB!11<-h5J@e& zB23)_q8{lR6dYE91toVg!|ow43@7lSgGN6dK`M~y1Hj0#E$%f7M;c(fNpJC`De?!< z67p0nfvkhUA%-tVo>PF(0gU63)PR9sD=_Klas@iF<*ake74T@vrCyScK|M!hwLg0U zA(ECY&|kJr+~9_)F^vurZppvyu&n#fc0<$+KdXn}d%Yf=>$JZ*3|Bk@ z%-61XW`qN6k&^oATB_i;$<~$(un|b0oP)*q79ffw{6w>s-C6bC0edyCLD3^kQ-cY2`i*gQ#oY31#52NSxXZ!x)K5AZ*Ec1tjFr# z)igFX=H;>!&qN8;TipPzO+&_%)AQC;XvqdR^2P)^ zC7(zjfMjmmQ-FYt=LfBzs^PwlV5CXr26h1TH{rwLfv0?=Wca9r3QGd~hG@$OqU`8! z0j~&_NVgtFPaR_G5kQe$?8hnuJ_Li}fRg<0ZkJK~h<|h4&ovazx04PW4tRIryl{vm zM(-#x5a~O{1z9qm_=wGT4GJdTdQC2ytjXa?+ z@tQfQ>A_qE$M}KE_m+oJRuc4;O=mb3+g$5Tm78P+L!VC%#pcL2+vDarddrS_ORd=j z1+BK8nwmdw$n}}N=TU{!RL1{n@5}?CT>rm+4wZH-gcNPIY)K4R#yLV|Pg$}|Oh{oO z`!;D0*)xSGLY4?+H`8KYL$;<&*%`txm@zZgbKP{#_xJlf&%e)K&(mKSX6Bx2uI015 zU+;VUMw@f*1-HS2`PAy3d^(ZOJl4!|+VVN)ec#)s;w#}EW`--;cdA6EoPuWws+4m_0bB z7HojsU#nkR=wtKDQ3KY|dghCe8lfj&nPmN6LQq_LAI{S4NfF-qWP*C4tYXh;D$G?G zBm=Q_w~2_6fKkho!%C30L5BTO3*51=k&(omSI%ge=7}{tOwqM>O$+UsHeWZ4myUS$ z?372I;zfzytnXeVaXfgQTv;Ap1cgD?@RTY!>-3G>r5{l;o!u;1%I_6KflUtF+)uJ~ zg#X23!bIPI=wg_w-uPE4CWLKGp3=D#3+Hg$R&rjKo2?PD*-soA_b-G`t@`x6`bPI&U>J_|HkG+(?407( zE3EfBfGmfn{K#Ibp+uOP+|%69@lqvIQn~-SD0$8>+*)(1i2k+vn>IFpP3y9UHKwJ{$)4XM1{P=yq1e^~jOI9Kk#m-<2hIh$8)Fl~MY{RTsh z>J+N#^hobqdH_y_>$~)J6wN$=wPvRx<#wY_3JH7GB~}$;dJI@4-=>~B z9`|?C-}0jVkqqrcijK!OqMgLC_3#_GD@pIIHw2`J!M=*SJsjPPj2G+j%O(?FB=#V> z^^FmyV5$#gl97yUes!B-w@#N?DH?|JG)leR^(5jZE7joD$?5SD;H*2wO6_-fkc>DM zKrJ?+<@7w*We+!^LDY|daZw*BH~lyv$Bd_fI8`nh8d#Q^@@)|-uA{Z{A6`3=s2gp9 zCLEX4D%<{CbkjpXd)(Nmw&O0LB?H4ef4A%-_xEQ9sU&-y-Pg~6wEg+9_S6D7o3b}v zXo^uqd~KZp+^xhzzg-t}sR5*W(E?=$_oESIn*iu}s+53f6I_$CS;9!m)mC9;x8T#8 zxB42mI<MOh`;h97TERmHRku6I^`R{nfu0LrF_xAn(Jg0f2cU%(bINo zhx+Q*cM7`Vpssthowno29hg09**BFRGZ!FEQoi)GCzWc%egd&;Wi58&qBIl5&K zUer>X7p>HdnN>u-!_4BG?_S^nRj+kV_=n6|3C^>upkUhK-enBK4 z+D-AA0moOMRIWYm*zZEdrM<`psFf;(A3(_U;>K6u`V$d(0oIhC4?=qH7F$N84kc8W zBrJk5tawZgUYB2y|VxEErKNE z(8U z-BW0j46lE_6@Ry|k)_ma3KxtQbR_5zn(mNDxoAK23U$YOPI|K@o!OC|x|nOfQ>Vx; zBGpm9sxW`cC72|8f;Wz*cYodE5y@-VJZL*s#iHexi zIz#buO^;9#^4{W=XVZWyVynjERO6jYns_Ts5ARjero^}r+H&G`e??Zr{mP{iH@MeNPQ zwj+QXmjI@%7QMw8l#~U&d5nEt^|Bg*kY%oY!a8$7>qT90IRQmA*UEL9{*PkzYh>rg zz%6{>h9*?D|Ng;99`M&yH2&!fOULiyVfMBmtjBUQc3{UM;g9CLRuO7D-b%mOA?-kw z!POPF=DZQGm`_f&w=x(8%4xY#wobCZB9K1URP>|)xDOMoqNI@5dstbFlqt_b$$NG& z^J|Khjq(mkf*{UdQfy!cOzPS~9KLtvYqb!=3-HFR*kCB-^@&^ZvA$7~4zmr@li#c0 zwDlyfD6W@Yn@XIwR#fpP^d=-yoP>2&1&ik;OB!v=pzUeho ze@(C3inAON?Lgct0wvM~eQNnF&cn#;N$GyYPwth|C6iDaHt%L$A+T{t@>Z>D;oH+f z)WX(?>P0Nv7Es?8B4~KC^46$0kVJkL{E~&UURa)Q1VfLQU$GGk0P*SRYs+6(QK^{C zHYYkmCd`|;=7cMj4PB~hgm)7Rd-i_P$gik6FzD!Fti>sH?jLC)?4zKA(CfOs%6VbPx->gTm6GBh`PR#VjVD3=y!XDh@~EE2K1`BqEZ%4Vi%3H zzn<8B2#|LYU?X%fIN{(tMWc=xS+WRst7Dq0f`T|X`HTqE-3c-OLJ`uVVt6(xMACzQ zW-5ho0xQSG!AjI$!m@I8rW}-+tydgq{Z?1f>#%PGU%XMaq7gW`W69SPB5lV=`cm}? zf36lqI578SqEg@WU9fkZYrTWI%e}rV!-?Ab*JPJBd-RRFb50Z@wznR@F-YGWyUiJ- zvrdWR5fI=J2dMs!f@d`0!gQVLe~yQd!MGhX0jfa#dc7XEIWYa#>lc!DRp*pxye`M(^--^k6Lk^)(~q}VVf)Dl@uh3iY4BV>tEHdPN- zv_5@t4d`eTq?+Zl*^+taYl=m?P5@p5}b znG8_7;JkgQf?z2COYyf3VXfO$5+sJ7S3n zf@RkdMPVH?Ery34yXVoQo2o_=NKPwfTMm=-$4lGOCQ|<5G=mAJnMDnGSx_o)W3>UN zau$k~MKZd6UP}s*c4W#Lz`%R|;^*uvh)dr@kKwZ8fmu@%hj|pN6!&y4!>xSTAx)3H z>1_-kJ}x5pxgcdwEc6S3EpMyN0xxBglloU0^OgA_DZS5N{)ogqFB=-1AMWTq3{v$A zMFR^hjxow7fLN`y2|f_3h{WmdJ>Z(yFY%>=n$#kwXq}mU>9Xfn<&bM9oi;PjqvKmC zC%t`!7)2*nC)^k4QwGpML-QVDXa-Ak5Cb{MwT+Cj&OCVjL*)p|+Khp@?tt1? z!NP5HER}&GhXi}2SNFJ}q)VF%g6;4fp@RzSd6!N6q_)9#bTXSLiX<jFKI~%)(bx{JbZeLt0wm*wGPHEwdJi$tORF za%RI0H}%Va=7-)Ioqk|9rVN|CJxD*0Neo?sj-j8^nYd?DiXx#4k7oP=x{ZxBmp|~D zL+iwByOpoKa&EIbqx??M!z`-O!s^cvmYK9=Ro`FVrX*1JW9RW!GU>B)c`!oCv!lfl zS}-BAw?%D&zxM{)(#z^K@|n{3k%E>|!ka^2{t<1JMPRh!L+CfqOE>g+{23zz3-r7q z+Dzl;CSyxxz$W1l{3cVPa|Dv&62S_I#B8OoTCHMKob(?Phf_dCeJ^5O@fk6)v)xj9 zox>Df9z?Ux5y&`Fu=#9QS|1dGQzn%P{0-?bQ9&;&5I6x(FW_`Xh&k%4u4K#{4d@_f!dUXml7AC03L6`=FET)`X@B& z(}f_Dis4uqBs~D{So4=s$WyS+&>QqLJsqJKUSLIi?xAts04<@^Ah^t$j^W^HQOw(* z8PAX}w)!v^axBat;Wk(K2>8(9zgjxAg=GrwZ1TtQu8+FecMG3L?GdF7-8PoN1#~6l zWT}rxttwF`o1_c!@<|-#&;OACOHD#rG zvY9y5oFlqYD4IlCE=I2ucdrUdb8_<&amD8@(C|#F9UxP$V+oEEhW?~pJ(Kwk3dUCf zmZ{@E3B-krZuSfwaNmxws!a$)a{Q;WJ*C8V0jwcW^orV!nNXSfEe0AOO7sQd1*i4g z5SHAPmDE%$3sR6nkmBab-w1fRMzk$6k%%A%r0c1s{C|2zOpVt;mj350*u{OYtkE783$|UkV;COM>|@rZH#OxQ)h6 zWf$X>E7e)oh@;nX5TSGT!`%`&&Kgv!3#@0S!?C{KJoM<0!$8U3QPMpOdfz90&i7~x zYI%SslLwS5ODFGC3|zB-cdoMgd1CcTc>J-iA@2EtkRrm41^UgwnXrsRHaV3ed{Qay?Mwz(0ZkUf zA|`QWh1f4vizszZzBAe0Ny!E)=#7bpV*p|**4J?<<9EAimzJ9M(cA5L|&|VO+f{~ zU8n%CKIv@BcV)sWPQ6uaU@nrIZ6yCxb@pRcij7x?PPawKh(#N!UQ8*xz8xcT#j&tV z3&QsW|5k227`V-3WRac$8=k_gbN zA)K@PwKEzVe{nf9dlcWOpy&gWr>zJQ9$)1nyy=vOw5{flqUPb?fGfcsnXo#3)Z>i2 zn(3uPpA9uAL%D%d%cAUb{Q2(?4@y~le{!mtHM7I_P8R1i9AW=*<=UaPIVUQ33VVSsa>%n-GFAhU4h0V4{T{VLwKx1Vjs9d%WaQ>X zVENZ?$aM_DMWWd$i*K`()IQs#!<7BEEhjuVrr^oZEceoX+jux}Jbe4sab7UX%d3tF zD)MVIY`j~zP?{NTlwz#?qn$I=o6y$!Eb6Sr?2XE_R#g*88bnTf|0amR?6N+Pu~r(WZihaN{X=JsvG*>|-zyz#9x zEMq{ApLa6Ip2mKUim277Gq;u7t$3O93BSzC6L9@YsijC?M_k9IWkZ~jbVx?1KMm4S zkcyB`vemW10aiO;O(JWNUNxpsFmNJJJ#f*LAN8x$zzkO7BCT1%cmxQfEV@yYlN9{@ zqH^GAFmRp?uW`AE;F=T-OV1{6yw)THmysT1%z*@^_#DIsEReWjTq}N1yL~BSX2~2( zlh~#GEjOR2?k0S$;mKSeHx546Qy<*P6Dd2_cwS!HV8s@Gagbs|M;9qrJ2^X#y)R)l zZn2xaHQ5mr;>Gq_T%NQbG2hScN`2{nB4aL#EvEzohWJ69UJ40<6RG@-D&xSb*Fp1Ha zRS({U7a&d~5v9JLuO8YzqcU6dQUaFNuhf?oYOeIR=O5}>s-6kv-MZ`s`N*~1EJNp; zpBYS>u3E14OU2>LHjJNAEPIb$<^j(GsMeH=ND!j3@TX)LMm~5#-&r)WGcp7s6x#f? zw>k;goVxS&Whbu-c?{1vhm1OlJZ%fdn0wUk_uW^H-z47zSn2zKd5u`z1Pwv|Cb}GY zNAQI&P_xFH43xlnq@BBtiC{mJp!7XZCQ0h>P~Gn}{jS#>qO}T&Qy=ecDb{y!V9VnC z^&!frk;_DIEACH^91_KNL(k0v1icLXL(cA^|BqT@jfc1#0|J`eg*HsRnEFa9n?W)v zLqG*{0w#pPie7?~Yu;PB3vMi3k*RC8F`-hhb8L9e2ut1sM4uRl~yp6N{ zsqFq@fkADPfKMZO8k45okUHvP-`;H9EQ|3JHtzBY`g~i&>r$^TY2`alio7NR;&&gp zlg_fud%nRGW^Ui<&V&>j&qlFsLdXQ6t-VAg(3A7`pVF()k<@|pJp0pvgyJznFn4;A z2T!sG+5%?p4Y|>pT1Ygo{k!$VXcErOKaMukuEN&nq`Wbv$DU$fE?B_ZDuiARS|L+H zelP~(B==@5T?|?Fr=g;la>)isDQY5jpW0rs(F0E!c#IoArT;Ctab1Z zDr89g?&=CE#yLF0Sz0n6fMoX_TFcMB&S--~@jRd8)e&T!jSdC(eG*Z|$h+xq2zxw+ z4A(#C0TwxYvg|j!>~9ys80pr>FgquawGx&p zWiDYE%($G0qF*x$GN?Db$^j!J79qFn#q2~^mh-`g=DS0h0j3+nZ}suAKl8Sd?$SM? z0&Vzfi`!W(?a2;hrN4?NBpw7#W$4=+61+&={<5ZG#|}o&~uPS{l}l(7Qx*Xts!%TDoszUgmeb|G1`-O%u4<0P6dj+iFMR5ZLczk}49C*qXZP$gE;&mW9+|Wyn_L-r^@b z)tESAoRh*DmXHn$-h75r&=e0l>F|iO4W1Lgd(inYbNt;%prlhMcJMLKH*r0xdti(u zFaBTyV!ING6i+1ofK`xi_e3Q{lQ-g>Q_3XSXCHTr0KT*nHlico`r<_qJ1q{cD7S{7 z(Iqkqg>^0Kb3qI^vB(V-WMZy-<}G&Gdw{<}e-laeF}N%4d)vMlY&h-HXb!_o;fvhY z4w(GSL0x*Y+D3MjTJxBmN*KGoynzexZTYJAOvS@tvDFLFOAJbcXq$Id*Y#|W9=-{& zx3W3+>Kcu4Pn%(Ur0Mq%0~#K>CO*lBp_2R5LQr662@TXj+v#BEWCU_vCysba)O;kg zB|cm^U7N(E-_Iq+E;-L3JcE>$JVI%ihrzC|SZ7_i!E;zwRSrSAWm?ZQ;2yOo6R2v| zvO^!2mqL^p&*0uG^jNEgBp0b`_wsJKsrTEAwg!-JKvDG(i=-NNdT*UL7Y$YdVo~M$ z%r0`qcA*f5F({?8Ijd@NSs5yceqH6`f$|S@Wpbz3<9BEMqvkS?$;OTbY#MR6JOa{A zKW!f&=}$JdumW{uk(~jpCegRZj?$MJi$`3|O!98xGWJO>Bte%B2a8~Rs1mVi@50$1 zwJ+&&@Iqd|s(l1xGh92CO$66t^`Qg-!y&{?-`D%t}UHhm-bXMc`pG~pQ-yPlaSOIRReyGr6@2WEQovuE&=bAgD8 zA#@P|+1ou|E51{}%IvkNN_K!ZK6o9{=O4i3(t=HBC?V`HFup{p&(?8$3|5rO^D`6( zfJZ(n0hBwjIc3AZGraF8SaS2Ur5=TayJjV91k(J5E*^r{+?AR%uuynXmuJ^WdD%@? zA3YpRl!`EYY{M5m*9=S~onrtGRu=FE408-Pq7iRk<`A=@$Vd&m^9TvNKEdN}8>1GCJ&+(pE7f4V0>c}NDt2Bm-hzTZ5<`jNafvqi}8T0cZ!3pI{@ylSzSVFO=XkqUaHtUk#l_KI-g<{RwM_!o@khFc{HQ2*+Cx9Xdc@ierwdN z^t&>2)G5Eo!nrFYXuvLN3~Z|Z?w!IWVtY}6;%@m zNIM<@oQgw0KsChcLUOMP-Pa19(E2J&Fv{FZhNtB;t3vZ4$5&bgRMNjMPSNnzAJU@; zKyPI$ds2`F!gBM468~D&p&ok!MSncowJ@!EF=Ao%^j%bi8!}GOR`+(lqU@*>UJ7_f z@sbYf-aD<{1bp_9HN#=Q(2K!`M6=r2HrB)#ykcu1}xrz?$-v zvUtxzMy>vqLAoDy*ZLw$#7HP`K@ZJk8m|uF@=)O^y${Fbp7DkM?o5ut&ZKZPJqn)Z zN#6cD|KJH*?geUF&NbEA*CoZmd&N|VwA^HKv9-I;-Co2{wRV-_!3pF49P%D;19&6{5jAqtdA6sS$kXXt*yDK z>Pstp4z>X{7kzf)em9xnmX~qmU0<){#~RfBx}*;Q54{N%J$t9oJ%nv9N9XPxlbe5# zYFWAfg%&FwM%FPPyACmA!$U(h5+i5N2srxmR|(S2eYR@s{1pn5uhn}LIX^@_aCsrV zVOKkU!`Ce??0eVyx83Bg`Fdku7@h;W-_cc*&xCVw!iz;xzX~z7D&eS@gi)?s_4@1mTJxsy#bY^sIvfo2jd5Cd{d9A# z`Upsj)|e(9g#>N!A9=KRkmTtkA-%PrS4(iRf<$cNgw-us*n-zqjUADmg6^LrA({N1Ins;UO7-ZC+U)S|R7w;qm>nu-wJVaMgdejApnl|Z8|={s!49g-7^%Dt z)?`DSU~b-;e35c3=WgNNwAFiy1Oze#b<&m}I)>dXTAqde-Hf9mL(bi4f+c6gDbqAO zx&-Ps(&Ck1KhfpiexjEgGIFA3h~u3kLR>y?Fs?q9P^8l#x%spnf8!*3$nZQXV%nNB z`O$)XuXL3$R3M;khY!($pc;!re0rF+Ga4{zI#2h&Mmja=(cH#=tw%o>KxHmzV7|xm zXhbUyed4jCTy0YAvP;pa7^wuNLJwIkIU_jRIaEh&V$qLyHG5@`B4v_(Vdbk`$@?es zXis#)$r)<>&>?fEZ$TS1QE8a%@iGTMmQe8BLYF$j0=Vu!XTrBgO1zdVJ@(PEX@_W4 z7w?cE`a#y%>0T=D_(X{#^sj=-? zetYxdlk=;mNcax8QLmNW{_`J6c+-}=npeWV-JSl+@3k_HiElz8wg3Kum$UXCWcu$~jZ^>Ir9D3l z2ma6}_s?)P|Bt6^#(Mtumq7mF|Ns5J1NHyM=l{w15DIt)im#X|#Aa{$1O7XsV{|J2 I?@PD-7Z{auNdN!< literal 0 HcmV?d00001 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"