├── .codespellrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ ├── question.md │ └── task.md └── workflows │ ├── bump-version-dev.yml │ ├── bump-version.yml │ ├── publish-package-dev.yml │ └── publish-package.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── .python-version ├── .ruff.toml ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── killpy ├── __init__.py ├── __main__.py ├── cleaners │ └── __init__.py ├── cli.py ├── commands │ ├── __init__.py │ └── clean.py ├── files │ └── __init__.py └── killers │ ├── __init__.py │ ├── conda_killer.py │ ├── killer.py │ ├── pipx_killer.py │ ├── poetry_killer.py │ ├── pyenv_killer.py │ └── venv_killer.py ├── pyproject.toml ├── show.gif └── uv.lock /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | exclude-file=uv.lock 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | ______________________________________________________________________ 2 | 3 | name: Bug report 4 | about: Create a report to help us improve 5 | title: "[BUG]" 6 | labels: bug 7 | assignees: Tlaloc-Es 8 | 9 | ______________________________________________________________________ 10 | 11 | **Issue description** 12 | Describe the issue you are experiencing in detail. 13 | 14 | **Steps to reproduce** 15 | Please provide detailed steps to reproduce the issue, including any code snippets, configuration settings, or error messages that you received. 16 | 17 | **Expected behavior** 18 | Explain what you expected to happen when you encountered the issue. 19 | 20 | **Actual behavior** 21 | Explain what actually happened when you encountered the issue. 22 | 23 | **Environment** 24 | 25 | - Python version: 26 | - Library version: 27 | - Operating system: 28 | 29 | **Additional context** 30 | Provide any additional context or information that may be relevant to the issue, such as relevant documentation links, screenshots, or error logs. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | ______________________________________________________________________ 2 | 3 | name: Feature request 4 | about: Suggest an idea for this project 5 | title: '' 6 | labels: enhancement 7 | assignees: Tlaloc-Es 8 | 9 | ______________________________________________________________________ 10 | 11 | **Feature description** 12 | Describe the feature you are requesting in detail. 13 | 14 | **Use case** 15 | Explain how you envision using this feature, and how it would benefit you and/or other users. 16 | 17 | **Proposed implementation** 18 | If you have ideas for how the feature could be implemented, please provide them here. This could include code samples, API designs, or anything else that would help the project maintainers understand your proposal. 19 | 20 | **Alternatives considered** 21 | Have you considered any alternatives to this feature request? If so, please describe them and explain why you believe this feature is a better solution. 22 | 23 | **Additional context** 24 | Provide any additional context or information that may be relevant to the feature request, such as relevant documentation links, screenshots, or examples from other projects. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | ______________________________________________________________________ 2 | 3 | name: Question 4 | about: Ask a question 5 | title: "[QUESTION]" 6 | labels: question 7 | assignees: Tlaloc-Es 8 | 9 | ______________________________________________________________________ 10 | 11 | **Question** 12 | State your question clearly and concisely. 13 | 14 | **Background** 15 | Provide some context or background information to help readers understand your question. This could include code snippets, relevant documentation links, or a brief summary of your project. 16 | 17 | **What I've tried** 18 | Explain what steps you've taken so far to try to answer the question yourself. This can help others understand what you've already done and avoid suggesting solutions that you've already tried. 19 | 20 | **Expected outcome** 21 | Explain what outcome you are expecting from the question. This can help others tailor their answers to your specific needs. 22 | 23 | **Additional context** 24 | Provide any additional context or information that may be relevant to your question, such as relevant documentation links, screenshots, or error logs. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | ______________________________________________________________________ 2 | 3 | name: Task 4 | about: Use this template to track general tasks, such as improving documentation or minor adjustments 5 | title: "[TASK]" 6 | labels: task 7 | assignees: Tlaloc-Es 8 | 9 | ______________________________________________________________________ 10 | 11 | **Task description** 12 | Provide a detailed description of the task to be completed. Be clear and concise about the objective. 13 | 14 | **Objective** 15 | What is the expected outcome of completing this task? 16 | Example: "Complete the missing sections of the project documentation, focusing on the models module." 17 | 18 | **Steps to complete** 19 | List the steps required to complete this task, if applicable. 20 | Example: 21 | 22 | 1. Review the current documentation structure. 23 | 1. Add missing sections for the `models` module. 24 | 1. Validate the documentation with `mkdocs serve`. 25 | 26 | **Priority** 27 | 28 | - [ ] Low 29 | - [ ] Medium 30 | - [ ] High 31 | 32 | **Relevant links/files** 33 | Include any links to documentation, code, or files that might help complete this task. 34 | 35 | **Additional context** 36 | Add any additional information that might be useful, such as dependencies, deadlines, or suggestions. 37 | -------------------------------------------------------------------------------- /.github/workflows/bump-version-dev.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Bump version dev 5 | 6 | on: 7 | push: 8 | branches: 9 | - dev 10 | 11 | jobs: 12 | bump_version: 13 | if: "!startsWith(github.event.head_commit.message, 'bump:')" 14 | runs-on: ubuntu-latest 15 | name: "Bump version and create changelog with commitizen" 16 | steps: 17 | - name: Check out 18 | uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 0 21 | token: "${{ secrets.GITHUB_TOKEN }}" 22 | - id: cz 23 | name: Create bump and changelog 24 | uses: commitizen-tools/commitizen-action@master 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | prerelease: beta 28 | - name: Print Version 29 | run: echo "Bumped to version ${{ steps.cz.outputs.version }}" 30 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Bump version 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | types: [closed] 12 | branches: 13 | - master 14 | 15 | jobs: 16 | bump_version: 17 | if: "!startsWith(github.event.head_commit.message, 'bump:')" 18 | runs-on: ubuntu-latest 19 | name: "Bump version and create changelog with commitizen" 20 | steps: 21 | - name: Check out 22 | uses: actions/checkout@v2 23 | with: 24 | fetch-depth: 0 25 | token: "${{ secrets.GITHUB_TOKEN }}" 26 | - id: cz 27 | name: Create bump and changelog 28 | uses: commitizen-tools/commitizen-action@master 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | - name: Print Version 32 | run: echo "Bumped to version ${{ steps.cz.outputs.version }}" 33 | -------------------------------------------------------------------------------- /.github/workflows/publish-package-dev.yml: -------------------------------------------------------------------------------- 1 | name: Publish beta package 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Bump version dev"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | 11 | jobs: 12 | publish-service-client-package: 13 | runs-on: ubuntu-latest 14 | name: "Publish package at PyPi" 15 | permissions: 16 | contents: write 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: recursive 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | - name: Install uv 23 | uses: astral-sh/setup-uv@v5 24 | - name: "build" 25 | env: 26 | UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN_DEV }} 27 | id: build 28 | run: | 29 | uv build 30 | uv publish --publish-url https://test.pypi.org/legacy/ 31 | -------------------------------------------------------------------------------- /.github/workflows/publish-package.yml: -------------------------------------------------------------------------------- 1 | name: Publish package 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Bump version"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | publish-service-client-package: 11 | runs-on: ubuntu-latest 12 | name: "Publish package at PyPi" 13 | permissions: 14 | contents: write 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | submodules: recursive 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | - name: Install uv 21 | uses: astral-sh/setup-uv@v5 22 | - name: "build" 23 | env: 24 | UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 25 | id: build 26 | run: | 27 | uv build 28 | uv publish 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-yaml 6 | - id: check-json 7 | - id: check-toml 8 | - id: end-of-file-fixer 9 | - id: trailing-whitespace 10 | 11 | - repo: https://github.com/executablebooks/mdformat 12 | rev: 0.7.21 13 | hooks: 14 | - id: mdformat 15 | 16 | - hooks: 17 | - id: commitizen 18 | repo: https://github.com/commitizen-tools/commitizen 19 | rev: v4.1.0 20 | 21 | - repo: https://github.com/astral-sh/ruff-pre-commit 22 | rev: v0.8.5 23 | hooks: 24 | - id: ruff 25 | args: [ --fix] 26 | - id: ruff-format 27 | 28 | - repo: https://github.com/pre-commit/mirrors-mypy 29 | rev: "v1.14.1" 30 | hooks: 31 | - id: mypy 32 | - repo: https://github.com/codespell-project/codespell 33 | rev: v2.3.0 34 | hooks: 35 | - id: codespell 36 | args: ["@.codespellrc"] 37 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: killpy 2 | name: killpy 3 | description: Clean repo 4 | entry: killpy clean 5 | language: python 6 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /.ruff.toml: -------------------------------------------------------------------------------- 1 | # Exclude a variety of commonly ignored directories. 2 | exclude = [ 3 | ".bzr", 4 | ".direnv", 5 | ".eggs", 6 | ".git", 7 | ".git-rewrite", 8 | ".hg", 9 | ".mypy_cache", 10 | ".nox", 11 | ".pants.d", 12 | ".pytype", 13 | ".ruff_cache", 14 | ".svn", 15 | ".tox", 16 | ".venv", 17 | "__pypackages__", 18 | "_build", 19 | "buck-out", 20 | "build", 21 | "dist", 22 | "node_modules", 23 | "venv", 24 | "src/interface", 25 | "tests" 26 | ] 27 | 28 | # Same as Black. 29 | line-length = 88 30 | indent-width = 4 31 | 32 | # Assume Python 3.10 33 | target-version = "py310" 34 | 35 | 36 | [lint.mccabe] 37 | max-complexity = 10 38 | 39 | [lint] 40 | # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. 41 | select = ["E", "F", "I", "N", "C", "W", "C90", "UP", "N", "PLC", "PLE", "PLR", "PLW"] 42 | ignore = [] 43 | 44 | # Allow fix for all enabled rules (when `--fix`) is provided. 45 | fixable = ["ALL"] 46 | unfixable = [] 47 | 48 | # Allow unused variables when underscore-prefixed. 49 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 50 | 51 | [lint.pylint] 52 | max-args = 6 53 | 54 | [lint.pydocstyle] 55 | convention = "pep257" 56 | 57 | [format] 58 | # Like Black, use double quotes for strings. 59 | quote-style = "double" 60 | 61 | # Like Black, indent with spaces, rather than tabs. 62 | indent-style = "space" 63 | 64 | # Like Black, respect magic trailing commas. 65 | skip-magic-trailing-comma = false 66 | 67 | # Like Black, automatically detect the appropriate line ending. 68 | line-ending = "auto" 69 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.venv": true, 4 | "**/__pycache__": true, 5 | "**/.pytest_cache": true, 6 | "**/.ipynb_checkpoints":true, 7 | "**/**.egg-info":true, 8 | "**/build":true, 9 | "**/.ruff_cache":true, 10 | "**/.mypy_cache":true 11 | }, 12 | "[python]":{ 13 | "editor.defaultFormatter": "ms-python.black-formatter", 14 | "editor.formatOnSave": true 15 | }, 16 | "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python" 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.15.4 (2025-02-07) 2 | 3 | ### Fix 4 | 5 | - set logging level to INFO in clean command for better visibility 6 | 7 | ## 0.15.3 (2025-02-07) 8 | 9 | ### Fix 10 | 11 | - enhance logging in clean command to report freed space 12 | 13 | ## 0.15.2 (2025-02-07) 14 | 15 | ### Fix 16 | 17 | - update killpy entry in pre-commit hooks to use clean command 18 | 19 | ## 0.15.1 (2025-02-07) 20 | 21 | ### Fix 22 | 23 | - update killpy entry point to use CLI 24 | 25 | ## 0.15.0 (2025-02-07) 26 | 27 | ### Feat 28 | 29 | - add clean command to killpy and integrate with CLI 30 | 31 | ## 0.14.1 (2025-01-23) 32 | 33 | ### Fix 34 | 35 | - the app breaks when 'pipx' is not installed. #14 36 | 37 | ## 0.14.0 (2025-01-17) 38 | 39 | ### Fix 40 | 41 | - forcebump 42 | 43 | ## 0.13.0 (2025-01-17) 44 | 45 | ### Feat 46 | 47 | - add GitHub Actions workflow for version bumping and changelog generation 48 | 49 | ### Fix 50 | 51 | - forcebump 52 | - update environment removal methods to use killers dictionary 53 | - update import path for TableApp in main module 54 | 55 | ### Refactor 56 | 57 | - separate entities 58 | 59 | ## 0.11.0b4 (2025-01-17) 60 | 61 | ### Feat 62 | 63 | - add GitHub Actions workflow for version bumping and changelog generation 64 | 65 | ### Fix 66 | 67 | - update environment removal methods to use killers dictionary 68 | - update import path for TableApp in main module 69 | 70 | ### Refactor 71 | 72 | - separate entities 73 | 74 | ## 0.11.0 (2025-01-16) 75 | 76 | ## 0.11.0b3 (2025-01-17) 77 | 78 | ## 0.11.0b2 (2025-01-17) 79 | 80 | ## 0.11.0b1 (2025-01-17) 81 | 82 | ## 0.11.0b0 (2025-01-17) 83 | 84 | ### Feat 85 | 86 | - add GitHub Actions workflow for version bumping and changelog generation 87 | - add support for pipx package management and enhance virtual environment tab functionality 88 | 89 | ### Refactor 90 | 91 | - separate entities 92 | 93 | ## 0.11.0 (2025-01-16) 94 | 95 | ### Feat 96 | 97 | - add support for pipx package management and enhance virtual environment tab functionality 98 | 99 | ## 0.10.0 (2025-01-06) 100 | 101 | ### Feat 102 | 103 | - enhance README and add support for listing Poetry virtual environments 104 | 105 | ## 0.9.0 (2025-01-05) 106 | 107 | ### Feat 108 | 109 | - add functionality to clean up __pycache__ directories and update README 110 | 111 | ## 0.8.4 (2025-01-05) 112 | 113 | ### Fix 114 | 115 | - update sorting logic in find_venvs functions to sort by size 116 | 117 | ## 0.8.3 (2025-01-05) 118 | 119 | ### Fix 120 | 121 | - handle FileNotFoundError in get_total_size and find_venvs functions close #4 122 | 123 | ## 0.8.2 (2025-01-05) 124 | 125 | ### Fix 126 | 127 | - try to execute with pipx 128 | 129 | ## 0.8.1 (2025-01-05) 130 | 131 | ### Fix 132 | 133 | - consolidate environment handling functions into __main__.py and remove envs_handler.py 134 | 135 | ## 0.8.0 (2025-01-05) 136 | 137 | ### Feat 138 | 139 | - update references from 'KillPy' to 'killpy' across the project 140 | 141 | ## 0.7.0 (2025-01-05) 142 | 143 | ### Feat 144 | 145 | - replace 'KillPy' with 'killpy' in workflow and pyproject.toml 146 | 147 | ## 0.6.0 (2025-01-05) 148 | 149 | ### Feat 150 | 151 | - add KillPy script entry point to pyproject.toml 152 | 153 | ## 0.5.0 (2025-01-05) 154 | 155 | ### Feat 156 | 157 | - enhance virtual environment management with deletion features and refactor code structure close #7 158 | 159 | ## 0.4.0 (2025-01-03) 160 | 161 | ### Feat 162 | 163 | - implement asynchronous searching for virtual environments close #2 164 | 165 | ## 0.3.0 (2025-01-03) 166 | 167 | ### Feat 168 | 169 | - add support for finding virtual environments with pyvenv and remove duplicates 170 | 171 | ### Fix 172 | 173 | - remove click dependency and update package version to 0.2.1 174 | 175 | ## 0.2.2 (2025-01-03) 176 | 177 | ### Fix 178 | 179 | - change key binding from 'ctrl+m' to 'space' for deleting .venv close #5 180 | 181 | ## 0.2.1 (2025-01-03) 182 | 183 | ### Fix 184 | 185 | - fails if conda is not installed #3 186 | 187 | ## 0.2.0 (2025-01-03) 188 | 189 | ### Feat 190 | 191 | - add support for listing and removing Conda environments in the app 192 | 193 | ## 0.1.7 (2025-01-02) 194 | 195 | ### Fix 196 | 197 | - add a banner to TableApp for enhanced user interface 198 | 199 | ## 0.1.6 (2025-01-02) 200 | 201 | ### Fix 202 | 203 | - enhance TableApp with improved venv display and deletion feedback 204 | 205 | ## 0.1.5 (2025-01-02) 206 | 207 | ### Refactor 208 | 209 | - reorganize imports and update deleted_cells type annotation in TableApp 210 | - improve find_venvs and get_total_size functions for better performance and readability 211 | 212 | ## 0.1.4 (2025-01-02) 213 | 214 | ### Fix 215 | 216 | - rename script entry point from pykill to killpy in pyproject.toml 217 | 218 | ## 0.1.3 (2025-01-02) 219 | 220 | ### Fix 221 | 222 | - rename script entry point from posewebcam to pykill in pyproject.toml 223 | 224 | ## 0.1.2 (2025-01-02) 225 | 226 | ### Fix 227 | 228 | - prevent find_venvs from traversing subdirectories within `.venv` folders 229 | 230 | ## 0.1.1 (2025-01-02) 231 | 232 | ### Fix 233 | 234 | - **cli**: fix command script 235 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Tlaloc-Es 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | ```plaintext 6 | █ ▄ ▄ █ █ ▄▄▄▄ ▄ ▄ ____ 7 | █▄▀ ▄ █ █ █ █ █ █ .'`_ o `;__, 8 | █ ▀▄ █ █ █ █▄▄▄▀ ▀▀▀█ . .'.'` '---' ' 9 | █ █ █ █ █ █ ▄ █ .`-...-'.'Reclaim disk space by cleaning unused Python environments. 10 | ▀ ▀▀▀ `-...-' 11 | ``` 12 | 13 |
14 | 15 | [![PyPI](https://img.shields.io/pypi/v/killpy.svg)](https://pypi.org/project/killpy/) 16 | [![Downloads](https://static.pepy.tech/personalized-badge/killpy?period=month&units=international_system&left_color=grey&right_color=blue&left_text=PyPi%20Downloads)](https://pepy.tech/project/killpy) 17 | [![Stars](https://img.shields.io/github/stars/Tlaloc-Es/killpy?color=yellow&style=flat)](https://github.com/Tlaloc-Es/killpy/stargazers) 18 | [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=🐍%20KillPy%20helps%20you%20reclaim%20disk%20space%20by%20detecting%20unused%20Python%20environments%20(.venv,%20poetry%20env,%20conda%20env)%20and%20pipx%20packages.%20Clean,%20organize%20and%20free%20up%20space%20effortlessly!%20🚀&url=https://github.com/Tlaloc-Es/KillPy) 19 | ![GitHub issue custom search](https://img.shields.io/github/issues-search?query=repo%3ATlaloc-Es%2Fkillpy%20is%3Aclosed&label=issues%20closed&labelColor=%20%237d89b0&color=%20%235d6b98) 20 | ![killpy in action](show.gif) 21 | 22 |
23 | 24 | # Delete .venv (Virtualenv, Poetry and Conda) Directories 25 | 26 | `killpy` is a simple tool designed to locate and delete `.venv` directories from your projects, including virtual environments created by Poetry and Conda. It can help you quickly clean up unnecessary virtual environments and save disk space. 27 | 28 | ## Features 29 | 30 | - **Automatic search:** Finds all .venv directories and any folders containing a pyvenv.cfg file recursively from the current working directory, as they are considered virtual environment folders. 31 | - **Support for Conda**: Lists all available Conda environments. 32 | - **Safe deletion:** Lists the directories to be deleted and asks for confirmation. 33 | - **Fast and lightweight:** Minimal dependencies for quick execution. 34 | 35 | ## Installation 36 | 37 | To install `killpy`, use pip: 38 | 39 | ```bash 40 | pip install killpy 41 | ``` 42 | 43 | ## Usage 44 | 45 | Run the following command to search for .venv directories and any folders containing a pyvenv.cfg file, as well as to list all Conda environments from the current directory and all its subdirectories recursively: 46 | 47 | ```bash 48 | killpy 49 | ``` 50 | 51 | With `pipx` 52 | 53 | ```bash 54 | pipx run killpy 55 | ``` 56 | 57 | With `uvx` 58 | 59 | ```bash 60 | uvx killpy 61 | ``` 62 | 63 | - To **close the application**, press `Ctrl+Q`. 64 | - To **mark a virtual environment for deletion**, press `D`. 65 | - To **confirm deletion of marked virtual environments**, press `Ctrl+D`. 66 | - To **delete a virtual environment immediately**, press `Shift+Delete`. 67 | - To **clean up __pycache__ folders**, press `P`. 68 | 69 | ## Pre-Commit 70 | 71 | To automatically use KillPy on each commit, you can add a pre-commit hook to your project. This will clean cache directories (like `__pycache__`) and other unnecessary files before every commit. 72 | 73 | ```yml 74 | - repo: https://github.com/Tlaloc-Es/KillPy 75 | rev: 0.15.4 76 | hooks: 77 | - id: killpy 78 | pass_filenames: false 79 | ``` 80 | 81 | ## Roadmap 82 | 83 | - [x] Delete `__pycache__` Files 84 | - [ ] Remove `dist` Folders and Build Artifacts 85 | - [ ] Clean Up Installed Package Cache 86 | - [ ] Delete `.egg-info` and `.dist-info` Files 87 | - [ ] Analyze and Remove Unused Dependencies 88 | - [ ] Optimize Disk Space in Python Projects 89 | 90 | ## Contributing 91 | 92 | Contributions are welcome! If you'd like to improve this tool, feel free to fork the repository and submit a pull request. 93 | 94 | 1. Fork the repository 95 | 1. Create a new branch for your feature: `git checkout -b my-feature` 96 | 1. Commit your changes: `git commit -m 'Add my feature'` 97 | 1. Push to the branch: `git push origin my-feature` 98 | 1. Submit a pull request 99 | 100 | ## License 101 | 102 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 103 | 104 | ______________________________________________________________________ 105 | 106 | Thank you for using `killpy`! If you find it useful, please star the repository on GitHub! 107 | -------------------------------------------------------------------------------- /killpy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tlaloc-Es/killpy/a67064c84c83d505a2d5c44f706f1b6a41f93b8a/killpy/__init__.py -------------------------------------------------------------------------------- /killpy/__main__.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from killpy.cli import TableApp 4 | from killpy.commands.clean import clean 5 | 6 | 7 | @click.group(invoke_without_command=True) 8 | @click.pass_context 9 | def cli(ctx): 10 | if not ctx.invoked_subcommand: 11 | app = TableApp() 12 | app.run() 13 | 14 | 15 | cli.add_command(clean) 16 | 17 | 18 | if __name__ == "__main__": 19 | cli() 20 | -------------------------------------------------------------------------------- /killpy/cleaners/__init__.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from pathlib import Path 3 | 4 | from killpy.files import get_total_size 5 | 6 | 7 | def remove_pycache(path: Path) -> int: 8 | total_freed_space = 0 9 | for pycache_dir in path.rglob("__pycache__"): 10 | try: 11 | total_freed_space += get_total_size(pycache_dir) 12 | shutil.rmtree(pycache_dir) 13 | except Exception: 14 | continue 15 | return total_freed_space 16 | -------------------------------------------------------------------------------- /killpy/cli.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from enum import Enum 3 | from pathlib import Path 4 | 5 | from textual.app import App, ComposeResult 6 | from textual.binding import Binding 7 | from textual.color import Gradient 8 | from textual.coordinate import Coordinate 9 | from textual.widgets import ( 10 | DataTable, 11 | Footer, 12 | Header, 13 | Label, 14 | ProgressBar, 15 | Static, 16 | TabbedContent, 17 | TabPane, 18 | ) 19 | 20 | from killpy.cleaners import remove_pycache 21 | from killpy.files import format_size 22 | from killpy.killers import ( 23 | CondaKiller, 24 | PipxKiller, 25 | PoetryKiller, 26 | PyenvKiller, 27 | VenvKiller, 28 | ) 29 | 30 | 31 | def is_venv_tab(func): 32 | def wrapper(self, *args, **kwargs): 33 | if self.query_one(TabbedContent).active == "venv-tab": 34 | return func(self, *args, **kwargs) 35 | 36 | return wrapper 37 | 38 | 39 | def is_pipx_tab(func): 40 | def wrapper(self, *args, **kwargs): 41 | if self.query_one(TabbedContent).active == "pipx-tab": 42 | return func(self, *args, **kwargs) 43 | 44 | return wrapper 45 | 46 | 47 | def remove_duplicates(venvs): 48 | seen_paths = set() 49 | unique_venvs = [] 50 | 51 | for venv in venvs: 52 | venv_path = venv[0] 53 | if venv_path not in seen_paths: 54 | unique_venvs.append(venv) 55 | seen_paths.add(venv_path) 56 | 57 | return unique_venvs 58 | 59 | 60 | class EnvStatus(Enum): 61 | DELETED = "DELETED" 62 | MARKED_TO_DELETE = "MARKED TO DELETE" 63 | 64 | 65 | class TableApp(App): 66 | deleted_cells: Coordinate = [] 67 | bytes_release: int = 0 68 | 69 | killers = { 70 | "conda_killer": CondaKiller(), 71 | "pipx_killer": PipxKiller(), 72 | "poetry_killer": PoetryKiller(Path.cwd()), 73 | "venv_killer": VenvKiller(Path.cwd()), 74 | "pyenv_killer": PyenvKiller(Path.cwd()), 75 | } 76 | 77 | BINDINGS = [ 78 | Binding(key="ctrl+q", action="quit", description="Exit"), 79 | Binding( 80 | key="d", 81 | action="mark_for_delete", 82 | description="Mark for deletion", 83 | show=True, 84 | ), 85 | Binding( 86 | key="ctrl+d", 87 | action="confirm_delete", 88 | description="Delete marked", 89 | show=True, 90 | ), 91 | Binding( 92 | key="shift+delete", 93 | action="delete_now", 94 | description="Delete immediately", 95 | show=True, 96 | ), 97 | Binding( 98 | key="p", 99 | action="clean_pycache", 100 | description="Clean __pycache__ dirs", 101 | show=True, 102 | ), 103 | Binding( 104 | key="u", 105 | action="uninstall_pipx", 106 | description="Uninstall pipx packages", 107 | show=True, 108 | ), 109 | ] 110 | 111 | CSS = """ 112 | #banner { 113 | color: white; 114 | border: heavy green; 115 | } 116 | 117 | TabbedContent #--content-tab-venv-tab { 118 | color: green; 119 | } 120 | 121 | TabbedContent #--content-tab-pipx-tab { 122 | color: yellow; 123 | } 124 | """ 125 | 126 | def compose(self) -> ComposeResult: 127 | yield Header() 128 | banner = Static( 129 | """ 130 | █ ▄ ▄ █ █ ▄▄▄▄ ▄ ▄ ____ 131 | █▄▀ ▄ █ █ █ █ █ █ .'`_ o `;__, 132 | █ ▀▄ █ █ █ █▄▄▄▀ ▀▀▀█ . .'.'` '---' ' A tool to delete 133 | █ █ █ █ █ █ ▄ █ .`-...-'.' .venv, Conda, Poetry environments 134 | ▀ ▀▀▀ `-...-'and clean up __pycache__ and temp files. 135 | """, 136 | id="banner", 137 | ) 138 | yield banner 139 | yield Label("Searching for virtual environments...") 140 | 141 | gradient = Gradient.from_colors( 142 | "#881177", 143 | "#aa3355", 144 | "#cc6666", 145 | "#ee9944", 146 | "#eedd00", 147 | "#99dd55", 148 | "#44dd88", 149 | "#22ccbb", 150 | "#00bbcc", 151 | "#0099cc", 152 | "#3366bb", 153 | "#663399", 154 | ) 155 | yield ProgressBar(total=100, gradient=gradient, show_eta=False) 156 | 157 | with TabbedContent(): 158 | with TabPane("Virtual Env", id="venv-tab"): 159 | yield DataTable(id="venv-table") 160 | with TabPane("Pipx", id="pipx-tab"): 161 | yield DataTable(id="pipx-table") 162 | 163 | yield Footer(show_command_palette=False) 164 | 165 | async def on_mount(self) -> None: 166 | self.title = """killpy""" 167 | await self.find_venvs() 168 | await self.find_pipx() 169 | 170 | def list_environments_of(self, killer: str): 171 | return asyncio.to_thread(self.killers[killer].list_environments) 172 | 173 | async def find_venvs(self): 174 | venvs = await asyncio.gather( 175 | self.list_environments_of("venv_killer"), 176 | self.list_environments_of("conda_killer"), 177 | self.list_environments_of("pyenv_killer"), 178 | self.list_environments_of("poetry_killer"), 179 | ) 180 | venvs = [env for sublist in venvs for env in sublist] 181 | venvs = remove_duplicates(venvs) 182 | 183 | table = self.query_one("#venv-table", DataTable) 184 | table.focus() 185 | table.add_columns( 186 | "Path", "Type", "Last Modified", "Size", "Size (Human Readable)", "Status" 187 | ) 188 | 189 | for venv in venvs: 190 | table.add_row(*venv) 191 | 192 | table.cursor_type = "row" 193 | table.zebra_stripes = True 194 | 195 | self.query_one(Label).update(f"Found {len(venvs)} .venv directories") 196 | 197 | async def find_pipx(self): 198 | venvs = await asyncio.gather(self.list_environments_of("pipx_killer")) 199 | 200 | venvs = [env for sublist in venvs for env in sublist] 201 | 202 | table = self.query_one("#pipx-table", DataTable) 203 | table.focus() 204 | table.add_columns("Package", "Size", "Size (Human Readable)", "Status") 205 | 206 | for venv in venvs: 207 | table.add_row(*venv) 208 | 209 | table.cursor_type = "row" 210 | table.zebra_stripes = True 211 | 212 | self.query_one(Label).update(f"Found {len(venvs)} .venv directories") 213 | 214 | async def action_clean_pycache(self): 215 | current_directory = Path.cwd() 216 | total_freed_space = await asyncio.to_thread(remove_pycache, current_directory) 217 | self.bytes_release += total_freed_space 218 | self.query_one(Label).update(f"{format_size(self.bytes_release)} deleted") 219 | self.bell() 220 | 221 | @is_venv_tab 222 | def action_confirm_delete(self): 223 | table = self.query_one("#venv-table", DataTable) 224 | for row_index in range(table.row_count): 225 | row_data = table.get_row_at(row_index) 226 | current_status = row_data[5] 227 | if current_status == EnvStatus.MARKED_TO_DELETE.value: 228 | cursor_cell = Coordinate(row_index, 0) 229 | if cursor_cell not in self.deleted_cells: 230 | path = row_data[0] 231 | self.bytes_release += row_data[3] 232 | env_type = row_data[1] 233 | self.delete_environment(path, env_type) 234 | table.update_cell_at((row_index, 5), EnvStatus.DELETED.value) 235 | self.deleted_cells.append(cursor_cell) 236 | self.query_one(Label).update(f"{format_size(self.bytes_release)} deleted") 237 | self.bell() 238 | 239 | @is_venv_tab 240 | def action_mark_for_delete(self): 241 | table = self.query_one("#venv-table", DataTable) 242 | 243 | cursor_cell = table.cursor_coordinate 244 | if cursor_cell: 245 | row_data = table.get_row_at(cursor_cell.row) 246 | current_status = row_data[5] 247 | if current_status == EnvStatus.DELETED.value: 248 | return 249 | elif current_status == EnvStatus.MARKED_TO_DELETE.value: 250 | table.update_cell_at((cursor_cell.row, 5), "") 251 | else: 252 | table.update_cell_at( 253 | (cursor_cell.row, 5), EnvStatus.MARKED_TO_DELETE.value 254 | ) 255 | 256 | @is_venv_tab 257 | def action_delete_now(self): 258 | table = self.query_one("#venv-table", DataTable) 259 | cursor_cell = table.cursor_coordinate 260 | if cursor_cell: 261 | if cursor_cell in self.deleted_cells: 262 | return 263 | row_data = table.get_row_at(cursor_cell.row) 264 | path = row_data[0] 265 | self.bytes_release += row_data[3] 266 | env_type = row_data[1] 267 | self.delete_environment(path, env_type) 268 | table.update_cell_at((cursor_cell.row, 5), EnvStatus.DELETED.value) 269 | self.query_one(Label).update(f"{format_size(self.bytes_release)} deleted") 270 | self.deleted_cells.append(cursor_cell) 271 | self.bell() 272 | 273 | @is_venv_tab 274 | def delete_environment(self, path, env_type): 275 | if env_type in {".venv", "pyvenv.cfg", "poetry"}: 276 | self.killers["venv_killer"].remove_environment(path) 277 | else: 278 | self.killers["conda_killer"].remove_environment(path) 279 | 280 | @is_pipx_tab 281 | def action_uninstall_pipx(self): 282 | table = self.query_one("#pipx-table", DataTable) 283 | cursor_cell = table.cursor_coordinate 284 | if cursor_cell: 285 | if cursor_cell in self.deleted_cells: 286 | return 287 | row_data = table.get_row_at(cursor_cell.row) 288 | package = row_data[0] 289 | size = row_data[1] 290 | 291 | self.killers["pipx_killer"].remove_environment(package) 292 | 293 | table.update_cell_at((cursor_cell.row, 3), EnvStatus.DELETED.value) 294 | self.deleted_cells.append(cursor_cell) 295 | self.bytes_release += size 296 | self.query_one(Label).update(f"{format_size(self.bytes_release)} deleted") 297 | 298 | self.bell() 299 | -------------------------------------------------------------------------------- /killpy/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tlaloc-Es/killpy/a67064c84c83d505a2d5c44f706f1b6a41f93b8a/killpy/commands/__init__.py -------------------------------------------------------------------------------- /killpy/commands/clean.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | 4 | import click 5 | 6 | from killpy.cleaners import remove_pycache 7 | from killpy.files import format_size 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | 11 | 12 | @click.command() 13 | @click.option("--path", default=Path.cwd(), help="Path to the directory to clean") 14 | def clean(path): 15 | path = Path(path) 16 | logging.info(f"Executing the clean command in {path}") 17 | total_freed_space = remove_pycache(path) 18 | logging.info(f"{format_size(total_freed_space)} deleted") 19 | -------------------------------------------------------------------------------- /killpy/files/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def get_total_size(path: Path) -> int: 5 | total_size = 0 6 | for f in path.rglob("*"): 7 | try: 8 | if f.is_file(): 9 | total_size += f.stat().st_size 10 | except FileNotFoundError: 11 | continue 12 | return total_size 13 | 14 | 15 | def format_size(size_in_bytes: int): 16 | if size_in_bytes >= 1 << 30: 17 | return f"{size_in_bytes / (1 << 30):.2f} GB" 18 | elif size_in_bytes >= 1 << 20: 19 | return f"{size_in_bytes / (1 << 20):.2f} MB" 20 | elif size_in_bytes >= 1 << 10: 21 | return f"{size_in_bytes / (1 << 10):.2f} KB" 22 | else: 23 | return f"{size_in_bytes} bytes" 24 | -------------------------------------------------------------------------------- /killpy/killers/__init__.py: -------------------------------------------------------------------------------- 1 | from killpy.killers.conda_killer import CondaKiller 2 | from killpy.killers.pipx_killer import PipxKiller 3 | from killpy.killers.poetry_killer import PoetryKiller 4 | from killpy.killers.pyenv_killer import PyenvKiller 5 | from killpy.killers.venv_killer import VenvKiller 6 | 7 | __all__ = ["CondaKiller", "PipxKiller", "PoetryKiller", "PyenvKiller", "VenvKiller"] 8 | -------------------------------------------------------------------------------- /killpy/killers/conda_killer.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from datetime import datetime 3 | from pathlib import Path 4 | 5 | from killpy.files import format_size, get_total_size 6 | from killpy.killers.killer import BaseKiller 7 | 8 | 9 | class CondaKiller(BaseKiller): 10 | def list_environments(self): 11 | try: 12 | result = subprocess.run( 13 | ["conda", "env", "list"], 14 | capture_output=True, 15 | text=True, 16 | check=True, 17 | ) 18 | 19 | venvs = [] 20 | for line in result.stdout.splitlines(): 21 | if line.strip() and not line.startswith("#"): 22 | env_info = line.strip().split() 23 | env_name = env_info[0] 24 | 25 | if "*" in env_info: 26 | continue 27 | 28 | dir_path = Path(env_info[1]) 29 | last_modified_timestamp = dir_path.stat().st_mtime 30 | last_modified = datetime.fromtimestamp( 31 | last_modified_timestamp 32 | ).strftime("%d/%m/%Y") 33 | 34 | size = get_total_size(dir_path) 35 | size_to_show = format_size(size) 36 | venvs.append((env_name, "Conda", last_modified, size, size_to_show)) 37 | 38 | venvs.sort(key=lambda x: x[3], reverse=True) 39 | return venvs 40 | 41 | except subprocess.CalledProcessError: 42 | return [] 43 | except Exception: 44 | return [] 45 | 46 | def remove_environment(self, env_to_delete): 47 | try: 48 | subprocess.run( 49 | ["conda", "env", "remove", "-n", env_to_delete], 50 | check=True, 51 | ) 52 | except subprocess.CalledProcessError as e: 53 | print(f"Error: {e}") 54 | -------------------------------------------------------------------------------- /killpy/killers/killer.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class BaseKiller(ABC): 5 | @abstractmethod 6 | def list_environments(self) -> list: 7 | raise NotImplementedError("Subclasses must implement this method") 8 | 9 | @abstractmethod 10 | def remove_environment(self, env_to_delete: str): 11 | raise NotImplementedError("Subclasses must implement this method") 12 | -------------------------------------------------------------------------------- /killpy/killers/pipx_killer.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import subprocess 4 | from pathlib import Path 5 | 6 | from killpy.files import format_size, get_total_size 7 | from killpy.killers.killer import BaseKiller 8 | 9 | 10 | class PipxKiller(BaseKiller): 11 | def list_environments(self): 12 | try: 13 | result = subprocess.run( 14 | ["pipx", "list", "--json"], 15 | capture_output=True, 16 | text=True, 17 | check=True, 18 | ) 19 | 20 | installed_packages = json.loads(result.stdout) 21 | 22 | packages_with_size = [] 23 | for package_name, package_data in installed_packages.get( 24 | "venvs", {} 25 | ).items(): 26 | bin_path = ( 27 | package_data.get("metadata", {}) 28 | .get("main_package", {}) 29 | .get("app_paths", [])[0] 30 | .get("__Path__", "") 31 | ) 32 | package_path = Path(bin_path).parent 33 | if package_path.exists(): 34 | total_size = get_total_size(package_path) 35 | formatted_size = format_size(total_size) 36 | packages_with_size.append( 37 | (package_name, total_size, formatted_size) 38 | ) 39 | 40 | return packages_with_size 41 | 42 | except subprocess.CalledProcessError as e: 43 | logging.error("Error: %s", e) 44 | return [] 45 | except Exception as e: 46 | logging.error("An error occurred: %s", e) 47 | return [] 48 | except subprocess.CalledProcessError as e: 49 | logging.error("Error: %s", e) 50 | return [] 51 | except Exception as e: 52 | logging.error("An error occurred: %s", e) 53 | return [] 54 | 55 | def remove_environment(self, env_to_delete): 56 | try: 57 | subprocess.run( 58 | ["pipx", "uninstall", env_to_delete], 59 | check=True, 60 | ) 61 | except subprocess.CalledProcessError as e: 62 | logging.error("Error: %s", e) 63 | -------------------------------------------------------------------------------- /killpy/killers/poetry_killer.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import platform 3 | import subprocess 4 | from datetime import datetime 5 | from pathlib import Path 6 | 7 | from killpy.files import format_size, get_total_size 8 | from killpy.killers.venv_killer import VenvKiller 9 | 10 | 11 | class PoetryKiller(VenvKiller): 12 | def __init__(self, root_dir): 13 | super().__init__(root_dir) 14 | 15 | def list_environments(self): 16 | try: 17 | if platform.system() == "Windows": 18 | poetry_venvs_dir = ( 19 | Path.home() / "AppData" / "Local" / "pypoetry" / "virtualenvs" 20 | ) 21 | else: 22 | poetry_venvs_dir = Path.home() / ".cache" / "pypoetry" / "virtualenvs" 23 | 24 | if not poetry_venvs_dir.exists(): 25 | logging.info( 26 | "No Poetry virtual environments directory found at %s", 27 | poetry_venvs_dir, 28 | ) 29 | return [] 30 | 31 | venvs = [] 32 | for venv_path in poetry_venvs_dir.iterdir(): 33 | if venv_path.is_dir(): 34 | last_modified_timestamp = venv_path.stat().st_mtime 35 | last_modified = datetime.fromtimestamp( 36 | last_modified_timestamp 37 | ).strftime("%d/%m/%Y") 38 | size = get_total_size(venv_path) 39 | size_to_show = format_size(size) 40 | venvs.append( 41 | (venv_path, "poetry", last_modified, size, size_to_show) 42 | ) 43 | 44 | venvs.sort(key=lambda x: x[3], reverse=True) 45 | return venvs 46 | 47 | except Exception as e: 48 | logging.error("An error occurred: %s", e) 49 | return [] 50 | except FileNotFoundError as e: 51 | logging.error("An error occurred: %s", e) 52 | return [] 53 | except subprocess.CalledProcessError as e: 54 | logging.error("Error while executing 'ls' command: %s", e) 55 | return [] 56 | -------------------------------------------------------------------------------- /killpy/killers/pyenv_killer.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from killpy.files import format_size, get_total_size 4 | from killpy.killers.venv_killer import VenvKiller 5 | 6 | 7 | class PyenvKiller(VenvKiller): 8 | def __init__(self, root_dir): 9 | super().__init__(root_dir) 10 | 11 | def list_environments(self): 12 | venvs = [] 13 | for dir_path in self.root_dir.rglob("pyvenv.cfg"): 14 | venv_dir = dir_path.parent 15 | last_modified_timestamp = dir_path.stat().st_mtime 16 | last_modified = datetime.fromtimestamp(last_modified_timestamp).strftime( 17 | "%d/%m/%Y" 18 | ) 19 | size = get_total_size(venv_dir) 20 | size_to_show = format_size(size) 21 | venvs.append((venv_dir, "pyvenv.cfg", last_modified, size, size_to_show)) 22 | 23 | venvs.sort(key=lambda x: x[3], reverse=True) 24 | return venvs 25 | -------------------------------------------------------------------------------- /killpy/killers/venv_killer.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from datetime import datetime 3 | 4 | from killpy.files import format_size, get_total_size 5 | from killpy.killers.killer import BaseKiller 6 | 7 | 8 | class VenvKiller(BaseKiller): 9 | def __init__(self, root_dir): 10 | self.root_dir = root_dir 11 | 12 | def list_environments(self): 13 | venvs = [] 14 | for dir_path in self.root_dir.rglob(".venv"): 15 | try: 16 | dir_path.resolve(strict=True) 17 | last_modified_timestamp = dir_path.stat().st_mtime 18 | last_modified = datetime.fromtimestamp( 19 | last_modified_timestamp 20 | ).strftime("%d/%m/%Y") 21 | size = get_total_size(dir_path) 22 | size_to_show = format_size(size) 23 | venvs.append((dir_path, ".venv", last_modified, size, size_to_show)) 24 | except FileNotFoundError: 25 | continue 26 | venvs.sort(key=lambda x: x[3], reverse=True) 27 | return venvs 28 | 29 | def remove_environment(self, env_to_delete): 30 | try: 31 | shutil.rmtree(env_to_delete) 32 | except FileNotFoundError: 33 | pass 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "killpy" 3 | version = "0.15.4" 4 | description = "List all .venv directories and Conda environments 🐍 on your system and check how much space they are using. You can then choose which ones to delete in order to free up space 🧹." 5 | readme = "README.md" 6 | classifiers = [ 7 | "Environment :: Console", 8 | "License :: OSI Approved :: MIT License", 9 | "Programming Language :: Python :: 3.12" 10 | ] 11 | dependencies = [ 12 | "click>=8.1.8", 13 | "rich>=13.9.4", 14 | "textual>=1.0.0", 15 | ] 16 | requires-python = ">=3.12" 17 | 18 | [tool.setuptools] 19 | license-files = [] 20 | 21 | [dependency-groups] 22 | dev = [ 23 | "commitizen>=4.1.0", 24 | "coverage>=7.6.10", 25 | "mypy>=1.14.0", 26 | "pre-commit>=4.0.1", 27 | "pytest>=8.3.4", 28 | "pytest-cov>=6.0.0", 29 | "ruff>=0.8.4", 30 | ] 31 | 32 | 33 | [tool.uv.workspace] 34 | members = ["q"] 35 | 36 | [tool.uv] 37 | package = true 38 | 39 | [tool.commitizen] 40 | name = "cz_conventional_commits" 41 | version = "0.15.4" 42 | version_files = [ 43 | "src/__version__.py", 44 | "pyproject.toml:version" 45 | ] 46 | 47 | [project.scripts] 48 | killpy = "killpy.__main__:cli" 49 | -------------------------------------------------------------------------------- /show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tlaloc-Es/killpy/a67064c84c83d505a2d5c44f706f1b6a41f93b8a/show.gif -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | 4 | [[package]] 5 | name = "argcomplete" 6 | version = "3.5.3" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/0c/be/6c23d80cb966fb8f83fb1ebfb988351ae6b0554d0c3a613ee4531c026597/argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392", size = 72999 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/c4/08/2a4db06ec3d203124c967fc89295e85a202e5cbbcdc08fd6a64b65217d1e/argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61", size = 43569 }, 11 | ] 12 | 13 | [[package]] 14 | name = "cfgv" 15 | version = "3.4.0" 16 | source = { registry = "https://pypi.org/simple" } 17 | sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } 18 | wheels = [ 19 | { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, 20 | ] 21 | 22 | [[package]] 23 | name = "charset-normalizer" 24 | version = "3.4.1" 25 | source = { registry = "https://pypi.org/simple" } 26 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, 29 | { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, 30 | { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, 31 | { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, 32 | { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, 33 | { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, 34 | { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, 35 | { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, 36 | { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, 37 | { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, 38 | { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, 39 | { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, 40 | { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, 41 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 42 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 43 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 44 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 45 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 46 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 47 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 48 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 49 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 50 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 51 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 52 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 53 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 54 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 55 | ] 56 | 57 | [[package]] 58 | name = "click" 59 | version = "8.1.8" 60 | source = { registry = "https://pypi.org/simple" } 61 | dependencies = [ 62 | { name = "colorama", marker = "sys_platform == 'win32'" }, 63 | ] 64 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 65 | wheels = [ 66 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 67 | ] 68 | 69 | [[package]] 70 | name = "colorama" 71 | version = "0.4.6" 72 | source = { registry = "https://pypi.org/simple" } 73 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 74 | wheels = [ 75 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 76 | ] 77 | 78 | [[package]] 79 | name = "commitizen" 80 | version = "4.1.0" 81 | source = { registry = "https://pypi.org/simple" } 82 | dependencies = [ 83 | { name = "argcomplete" }, 84 | { name = "charset-normalizer" }, 85 | { name = "colorama" }, 86 | { name = "decli" }, 87 | { name = "jinja2" }, 88 | { name = "packaging" }, 89 | { name = "pyyaml" }, 90 | { name = "questionary" }, 91 | { name = "termcolor" }, 92 | { name = "tomlkit" }, 93 | ] 94 | sdist = { url = "https://files.pythonhosted.org/packages/7a/c5/66f1b977b48501a33f5fd33253aba14786483b08aba987718d272e99e732/commitizen-4.1.0.tar.gz", hash = "sha256:4f2d9400ec411aec1c738d4c63fc7fd5807cd6ddf6be970869e03e68b88ff718", size = 51252 } 95 | wheels = [ 96 | { url = "https://files.pythonhosted.org/packages/48/f7/7f70adfbf3553ffdbe391eaacde72b21dbc1b4226ae56ca32e8ded1bf70b/commitizen-4.1.0-py3-none-any.whl", hash = "sha256:2e6c5fbd442cab4bcc5a04bc86ef2196ef84bcf611317d6c596e87f5bb4c09f5", size = 72282 }, 97 | ] 98 | 99 | [[package]] 100 | name = "coverage" 101 | version = "7.6.10" 102 | source = { registry = "https://pypi.org/simple" } 103 | sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } 104 | wheels = [ 105 | { url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 }, 106 | { url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 }, 107 | { url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 }, 108 | { url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572 }, 109 | { url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639 }, 110 | { url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072 }, 111 | { url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386 }, 112 | { url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054 }, 113 | { url = "https://files.pythonhosted.org/packages/5c/74/83ae4151c170d8bd071924f212add22a0e62a7fe2b149edf016aeecad17c/coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359", size = 210904 }, 114 | { url = "https://files.pythonhosted.org/packages/c3/54/de0893186a221478f5880283119fc40483bc460b27c4c71d1b8bba3474b9/coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247", size = 211692 }, 115 | { url = "https://files.pythonhosted.org/packages/25/6d/31883d78865529257bf847df5789e2ae80e99de8a460c3453dbfbe0db069/coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9", size = 208308 }, 116 | { url = "https://files.pythonhosted.org/packages/70/22/3f2b129cc08de00c83b0ad6252e034320946abfc3e4235c009e57cfeee05/coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b", size = 208565 }, 117 | { url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083 }, 118 | { url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235 }, 119 | { url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220 }, 120 | { url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847 }, 121 | { url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922 }, 122 | { url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783 }, 123 | { url = "https://files.pythonhosted.org/packages/3d/f7/64d3298b2baf261cb35466000628706ce20a82d42faf9b771af447cd2b76/coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6", size = 210965 }, 124 | { url = "https://files.pythonhosted.org/packages/d5/58/ec43499a7fc681212fe7742fe90b2bc361cdb72e3181ace1604247a5b24d/coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e", size = 211719 }, 125 | { url = "https://files.pythonhosted.org/packages/ab/c9/f2857a135bcff4330c1e90e7d03446b036b2363d4ad37eb5e3a47bbac8a6/coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe", size = 209050 }, 126 | { url = "https://files.pythonhosted.org/packages/aa/b3/f840e5bd777d8433caa9e4a1eb20503495709f697341ac1a8ee6a3c906ad/coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273", size = 209321 }, 127 | { url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039 }, 128 | { url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758 }, 129 | { url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119 }, 130 | { url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597 }, 131 | { url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473 }, 132 | { url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 }, 133 | { url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 }, 134 | { url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 }, 135 | ] 136 | 137 | [[package]] 138 | name = "decli" 139 | version = "0.6.2" 140 | source = { registry = "https://pypi.org/simple" } 141 | sdist = { url = "https://files.pythonhosted.org/packages/3d/a0/a4658f93ecb589f479037b164dc13c68d108b50bf6594e54c820749f97ac/decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f", size = 7424 } 142 | wheels = [ 143 | { url = "https://files.pythonhosted.org/packages/bf/70/3ea48dc9e958d7d66c44c9944809181f1ca79aaef25703c023b5092d34ff/decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed", size = 7854 }, 144 | ] 145 | 146 | [[package]] 147 | name = "distlib" 148 | version = "0.3.9" 149 | source = { registry = "https://pypi.org/simple" } 150 | sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } 151 | wheels = [ 152 | { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, 153 | ] 154 | 155 | [[package]] 156 | name = "filelock" 157 | version = "3.16.1" 158 | source = { registry = "https://pypi.org/simple" } 159 | sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } 160 | wheels = [ 161 | { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, 162 | ] 163 | 164 | [[package]] 165 | name = "identify" 166 | version = "2.6.4" 167 | source = { registry = "https://pypi.org/simple" } 168 | sdist = { url = "https://files.pythonhosted.org/packages/49/a5/7de3053524ee006b91099968d7ecb2e0b420f7ae728094394c33e8a2a2b9/identify-2.6.4.tar.gz", hash = "sha256:285a7d27e397652e8cafe537a6cc97dd470a970f48fb2e9d979aa38eae5513ac", size = 99209 } 169 | wheels = [ 170 | { url = "https://files.pythonhosted.org/packages/a2/9d/52f036403ae86474804f699c0d084b4b071e333a390b20269bb8accc65e0/identify-2.6.4-py2.py3-none-any.whl", hash = "sha256:993b0f01b97e0568c179bb9196391ff391bfb88a99099dbf5ce392b68f42d0af", size = 99072 }, 171 | ] 172 | 173 | [[package]] 174 | name = "iniconfig" 175 | version = "2.0.0" 176 | source = { registry = "https://pypi.org/simple" } 177 | sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } 178 | wheels = [ 179 | { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, 180 | ] 181 | 182 | [[package]] 183 | name = "jinja2" 184 | version = "3.1.5" 185 | source = { registry = "https://pypi.org/simple" } 186 | dependencies = [ 187 | { name = "markupsafe" }, 188 | ] 189 | sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } 190 | wheels = [ 191 | { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, 192 | ] 193 | 194 | [[package]] 195 | name = "killpy" 196 | version = "0.14.1" 197 | source = { editable = "." } 198 | dependencies = [ 199 | { name = "click" }, 200 | { name = "rich" }, 201 | { name = "textual" }, 202 | ] 203 | 204 | [package.dev-dependencies] 205 | dev = [ 206 | { name = "commitizen" }, 207 | { name = "coverage" }, 208 | { name = "mypy" }, 209 | { name = "pre-commit" }, 210 | { name = "pytest" }, 211 | { name = "pytest-cov" }, 212 | { name = "ruff" }, 213 | ] 214 | 215 | [package.metadata] 216 | requires-dist = [ 217 | { name = "click", specifier = ">=8.1.8" }, 218 | { name = "rich", specifier = ">=13.9.4" }, 219 | { name = "textual", specifier = ">=1.0.0" }, 220 | ] 221 | 222 | [package.metadata.requires-dev] 223 | dev = [ 224 | { name = "commitizen", specifier = ">=4.1.0" }, 225 | { name = "coverage", specifier = ">=7.6.10" }, 226 | { name = "mypy", specifier = ">=1.14.0" }, 227 | { name = "pre-commit", specifier = ">=4.0.1" }, 228 | { name = "pytest", specifier = ">=8.3.4" }, 229 | { name = "pytest-cov", specifier = ">=6.0.0" }, 230 | { name = "ruff", specifier = ">=0.8.4" }, 231 | ] 232 | 233 | [[package]] 234 | name = "linkify-it-py" 235 | version = "2.0.3" 236 | source = { registry = "https://pypi.org/simple" } 237 | dependencies = [ 238 | { name = "uc-micro-py" }, 239 | ] 240 | sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946 } 241 | wheels = [ 242 | { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820 }, 243 | ] 244 | 245 | [[package]] 246 | name = "markdown-it-py" 247 | version = "3.0.0" 248 | source = { registry = "https://pypi.org/simple" } 249 | dependencies = [ 250 | { name = "mdurl" }, 251 | ] 252 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 253 | wheels = [ 254 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 255 | ] 256 | 257 | [package.optional-dependencies] 258 | linkify = [ 259 | { name = "linkify-it-py" }, 260 | ] 261 | plugins = [ 262 | { name = "mdit-py-plugins" }, 263 | ] 264 | 265 | [[package]] 266 | name = "markupsafe" 267 | version = "3.0.2" 268 | source = { registry = "https://pypi.org/simple" } 269 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 270 | wheels = [ 271 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, 272 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, 273 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, 274 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, 275 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, 276 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, 277 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, 278 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, 279 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, 280 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, 281 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, 282 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, 283 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, 284 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, 285 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, 286 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, 287 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, 288 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, 289 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, 290 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, 291 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, 292 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, 293 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, 294 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, 295 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, 296 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, 297 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, 298 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, 299 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, 300 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, 301 | ] 302 | 303 | [[package]] 304 | name = "mdit-py-plugins" 305 | version = "0.4.2" 306 | source = { registry = "https://pypi.org/simple" } 307 | dependencies = [ 308 | { name = "markdown-it-py" }, 309 | ] 310 | sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } 311 | wheels = [ 312 | { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, 313 | ] 314 | 315 | [[package]] 316 | name = "mdurl" 317 | version = "0.1.2" 318 | source = { registry = "https://pypi.org/simple" } 319 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 320 | wheels = [ 321 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 322 | ] 323 | 324 | [[package]] 325 | name = "mypy" 326 | version = "1.14.1" 327 | source = { registry = "https://pypi.org/simple" } 328 | dependencies = [ 329 | { name = "mypy-extensions" }, 330 | { name = "typing-extensions" }, 331 | ] 332 | sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051 } 333 | wheels = [ 334 | { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668 }, 335 | { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060 }, 336 | { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167 }, 337 | { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341 }, 338 | { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991 }, 339 | { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016 }, 340 | { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097 }, 341 | { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728 }, 342 | { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965 }, 343 | { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660 }, 344 | { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198 }, 345 | { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 }, 346 | { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 }, 347 | ] 348 | 349 | [[package]] 350 | name = "mypy-extensions" 351 | version = "1.0.0" 352 | source = { registry = "https://pypi.org/simple" } 353 | sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } 354 | wheels = [ 355 | { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, 356 | ] 357 | 358 | [[package]] 359 | name = "nodeenv" 360 | version = "1.9.1" 361 | source = { registry = "https://pypi.org/simple" } 362 | sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } 363 | wheels = [ 364 | { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, 365 | ] 366 | 367 | [[package]] 368 | name = "packaging" 369 | version = "24.2" 370 | source = { registry = "https://pypi.org/simple" } 371 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 372 | wheels = [ 373 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 374 | ] 375 | 376 | [[package]] 377 | name = "platformdirs" 378 | version = "4.3.6" 379 | source = { registry = "https://pypi.org/simple" } 380 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } 381 | wheels = [ 382 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, 383 | ] 384 | 385 | [[package]] 386 | name = "pluggy" 387 | version = "1.5.0" 388 | source = { registry = "https://pypi.org/simple" } 389 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 390 | wheels = [ 391 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 392 | ] 393 | 394 | [[package]] 395 | name = "pre-commit" 396 | version = "4.0.1" 397 | source = { registry = "https://pypi.org/simple" } 398 | dependencies = [ 399 | { name = "cfgv" }, 400 | { name = "identify" }, 401 | { name = "nodeenv" }, 402 | { name = "pyyaml" }, 403 | { name = "virtualenv" }, 404 | ] 405 | sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } 406 | wheels = [ 407 | { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, 408 | ] 409 | 410 | [[package]] 411 | name = "prompt-toolkit" 412 | version = "3.0.48" 413 | source = { registry = "https://pypi.org/simple" } 414 | dependencies = [ 415 | { name = "wcwidth" }, 416 | ] 417 | sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } 418 | wheels = [ 419 | { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, 420 | ] 421 | 422 | [[package]] 423 | name = "pygments" 424 | version = "2.18.0" 425 | source = { registry = "https://pypi.org/simple" } 426 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } 427 | wheels = [ 428 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, 429 | ] 430 | 431 | [[package]] 432 | name = "pytest" 433 | version = "8.3.4" 434 | source = { registry = "https://pypi.org/simple" } 435 | dependencies = [ 436 | { name = "colorama", marker = "sys_platform == 'win32'" }, 437 | { name = "iniconfig" }, 438 | { name = "packaging" }, 439 | { name = "pluggy" }, 440 | ] 441 | sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } 442 | wheels = [ 443 | { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, 444 | ] 445 | 446 | [[package]] 447 | name = "pytest-cov" 448 | version = "6.0.0" 449 | source = { registry = "https://pypi.org/simple" } 450 | dependencies = [ 451 | { name = "coverage" }, 452 | { name = "pytest" }, 453 | ] 454 | sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } 455 | wheels = [ 456 | { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, 457 | ] 458 | 459 | [[package]] 460 | name = "pyyaml" 461 | version = "6.0.2" 462 | source = { registry = "https://pypi.org/simple" } 463 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 464 | wheels = [ 465 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 466 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 467 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 468 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 469 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 470 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 471 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 472 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 473 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 474 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 475 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 476 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 477 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 478 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 479 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 480 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 481 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 482 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 483 | ] 484 | 485 | [[package]] 486 | name = "questionary" 487 | version = "2.1.0" 488 | source = { registry = "https://pypi.org/simple" } 489 | dependencies = [ 490 | { name = "prompt-toolkit" }, 491 | ] 492 | sdist = { url = "https://files.pythonhosted.org/packages/a8/b8/d16eb579277f3de9e56e5ad25280fab52fc5774117fb70362e8c2e016559/questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587", size = 26775 } 493 | wheels = [ 494 | { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747 }, 495 | ] 496 | 497 | [[package]] 498 | name = "rich" 499 | version = "13.9.4" 500 | source = { registry = "https://pypi.org/simple" } 501 | dependencies = [ 502 | { name = "markdown-it-py" }, 503 | { name = "pygments" }, 504 | ] 505 | sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } 506 | wheels = [ 507 | { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, 508 | ] 509 | 510 | [[package]] 511 | name = "ruff" 512 | version = "0.8.4" 513 | source = { registry = "https://pypi.org/simple" } 514 | sdist = { url = "https://files.pythonhosted.org/packages/34/37/9c02181ef38d55b77d97c68b78e705fd14c0de0e5d085202bb2b52ce5be9/ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8", size = 3402103 } 515 | wheels = [ 516 | { url = "https://files.pythonhosted.org/packages/05/67/f480bf2f2723b2e49af38ed2be75ccdb2798fca7d56279b585c8f553aaab/ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60", size = 10546415 }, 517 | { url = "https://files.pythonhosted.org/packages/eb/7a/5aba20312c73f1ce61814e520d1920edf68ca3b9c507bd84d8546a8ecaa8/ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac", size = 10346113 }, 518 | { url = "https://files.pythonhosted.org/packages/76/f4/c41de22b3728486f0aa95383a44c42657b2db4062f3234ca36fc8cf52d8b/ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296", size = 9943564 }, 519 | { url = "https://files.pythonhosted.org/packages/0e/f0/afa0d2191af495ac82d4cbbfd7a94e3df6f62a04ca412033e073b871fc6d/ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643", size = 10805522 }, 520 | { url = "https://files.pythonhosted.org/packages/12/57/5d1e9a0fd0c228e663894e8e3a8e7063e5ee90f8e8e60cf2085f362bfa1a/ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e", size = 10306763 }, 521 | { url = "https://files.pythonhosted.org/packages/04/df/f069fdb02e408be8aac6853583572a2873f87f866fe8515de65873caf6b8/ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3", size = 11359574 }, 522 | { url = "https://files.pythonhosted.org/packages/d3/04/37c27494cd02e4a8315680debfc6dfabcb97e597c07cce0044db1f9dfbe2/ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f", size = 12094851 }, 523 | { url = "https://files.pythonhosted.org/packages/81/b1/c5d7fb68506cab9832d208d03ea4668da9a9887a4a392f4f328b1bf734ad/ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604", size = 11655539 }, 524 | { url = "https://files.pythonhosted.org/packages/ef/38/8f8f2c8898dc8a7a49bc340cf6f00226917f0f5cb489e37075bcb2ce3671/ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf", size = 12912805 }, 525 | { url = "https://files.pythonhosted.org/packages/06/dd/fa6660c279f4eb320788876d0cff4ea18d9af7d9ed7216d7bd66877468d0/ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720", size = 11205976 }, 526 | { url = "https://files.pythonhosted.org/packages/a8/d7/de94cc89833b5de455750686c17c9e10f4e1ab7ccdc5521b8fe911d1477e/ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae", size = 10792039 }, 527 | { url = "https://files.pythonhosted.org/packages/6d/15/3e4906559248bdbb74854af684314608297a05b996062c9d72e0ef7c7097/ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7", size = 10400088 }, 528 | { url = "https://files.pythonhosted.org/packages/a2/21/9ed4c0e8133cb4a87a18d470f534ad1a8a66d7bec493bcb8bda2d1a5d5be/ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111", size = 10900814 }, 529 | { url = "https://files.pythonhosted.org/packages/0d/5d/122a65a18955bd9da2616b69bc839351f8baf23b2805b543aa2f0aed72b5/ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8", size = 11268828 }, 530 | { url = "https://files.pythonhosted.org/packages/43/a9/1676ee9106995381e3d34bccac5bb28df70194167337ed4854c20f27c7ba/ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835", size = 8805621 }, 531 | { url = "https://files.pythonhosted.org/packages/10/98/ed6b56a30ee76771c193ff7ceeaf1d2acc98d33a1a27b8479cbdb5c17a23/ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d", size = 9660086 }, 532 | { url = "https://files.pythonhosted.org/packages/13/9f/026e18ca7d7766783d779dae5e9c656746c6ede36ef73c6d934aaf4a6dec/ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08", size = 9074500 }, 533 | ] 534 | 535 | [[package]] 536 | name = "termcolor" 537 | version = "2.5.0" 538 | source = { registry = "https://pypi.org/simple" } 539 | sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 } 540 | wheels = [ 541 | { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, 542 | ] 543 | 544 | [[package]] 545 | name = "textual" 546 | version = "1.0.0" 547 | source = { registry = "https://pypi.org/simple" } 548 | dependencies = [ 549 | { name = "markdown-it-py", extra = ["linkify", "plugins"] }, 550 | { name = "platformdirs" }, 551 | { name = "rich" }, 552 | { name = "typing-extensions" }, 553 | ] 554 | sdist = { url = "https://files.pythonhosted.org/packages/1f/b6/59b1de04bb4dca0f21ed7ba0b19309ed7f3f5de4396edf20cc2855e53085/textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399", size = 1532733 } 555 | wheels = [ 556 | { url = "https://files.pythonhosted.org/packages/ac/bb/5fb6656c625019cd653d5215237d7cd6e0b12e7eae4195c3d1c91b2136fc/textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f", size = 660456 }, 557 | ] 558 | 559 | [[package]] 560 | name = "tomlkit" 561 | version = "0.13.2" 562 | source = { registry = "https://pypi.org/simple" } 563 | sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } 564 | wheels = [ 565 | { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, 566 | ] 567 | 568 | [[package]] 569 | name = "typing-extensions" 570 | version = "4.12.2" 571 | source = { registry = "https://pypi.org/simple" } 572 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 573 | wheels = [ 574 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 575 | ] 576 | 577 | [[package]] 578 | name = "uc-micro-py" 579 | version = "1.0.3" 580 | source = { registry = "https://pypi.org/simple" } 581 | sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043 } 582 | wheels = [ 583 | { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229 }, 584 | ] 585 | 586 | [[package]] 587 | name = "virtualenv" 588 | version = "20.28.0" 589 | source = { registry = "https://pypi.org/simple" } 590 | dependencies = [ 591 | { name = "distlib" }, 592 | { name = "filelock" }, 593 | { name = "platformdirs" }, 594 | ] 595 | sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } 596 | wheels = [ 597 | { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, 598 | ] 599 | 600 | [[package]] 601 | name = "wcwidth" 602 | version = "0.2.13" 603 | source = { registry = "https://pypi.org/simple" } 604 | sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } 605 | wheels = [ 606 | { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, 607 | ] 608 | --------------------------------------------------------------------------------