├── .github └── workflows │ ├── python-publish.yml │ └── python-test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── pyproject.toml ├── static └── yakari.png ├── test ├── __init__.py ├── conftest.py ├── test_app.py └── test_types.py ├── uv.lock └── yakari ├── .DS_Store ├── __init__.py ├── app.css ├── app.py ├── cli.py ├── constants.py ├── rich_render.py ├── screens ├── __init__.py ├── choice_argument.py ├── menu.py ├── results.py └── value_argument.py ├── types.py └── widgets ├── __init__.py ├── argument_input.py ├── command_runner.py ├── footer.py ├── suggestions.py └── tags.py /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | id-token: write 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Install uv 19 | uses: astral-sh/setup-uv@v4 20 | with: 21 | # Install a specific version of uv. 22 | version: "0.5.10" 23 | 24 | - name: Set up Python 25 | run: uv python install 26 | 27 | - name: Build the project 28 | run: uv build 29 | 30 | - name: Publish the package 31 | run: uv publish 32 | -------------------------------------------------------------------------------- /.github/workflows/python-test.yml: -------------------------------------------------------------------------------- 1 | name: Run Python Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: true 19 | 20 | - name: Install uv 21 | uses: astral-sh/setup-uv@v4 22 | with: 23 | # Install a specific version of uv. 24 | version: "0.5.10" 25 | 26 | - name: Set up Python 27 | run: uv python install 28 | 29 | - name: Install the project 30 | run: uv sync --all-extras --dev 31 | 32 | - name: Run tests 33 | # For example, using `pytest` 34 | run: uv run pytest 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "menus"] 2 | path = menus 3 | url = git@github.com:vlandeiro/yakari-menus.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yakari 2 | 3 | [![pytest](https://github.com/vlandeiro/yakari/actions/workflows/python-test.yml/badge.svg)](https://github.com/vlandeiro/yakari/actions/workflows/python-test.yml) 4 | 5 | Transform complex command-line interfaces into guided, interactive experiences. 6 | Yakari helps users build commands step by step, making CLI tools more accessible 7 | and user-friendly. 8 | 9 | 10 | ## Getting started 11 | 12 | ### Usage 13 | 14 | 15 | 16 | ``` bash 17 | usage: ykr [-h] [-d] [-n] command_name 18 | 19 | positional arguments: 20 | command_name Name of the command to execute 21 | 22 | options: 23 | -h, --help show this help message and exit 24 | -d, --dry-run If toggled, Yakari only prints the command rather than running it. 25 | -n, --native When toggled, run the command in the original shell instead of 26 | within the Yakari TUI. 27 | ``` 28 | 29 | ### Try it out! 30 | 31 | With [uv](https://github.com/astral-sh/uv): 32 | 33 | ``` bash 34 | uvx --from yakari ykr demo 35 | ``` 36 | 37 | will start a demo that showcases the different types or arguments and commands 38 | supported by Yakari. This demo only run `echo` commands so it's safe to use 39 | anywhere. 40 | 41 | ``` bash 42 | uvx --from yakari ykr git 43 | ``` 44 | 45 | will start a Terminal User Interface (TUI) with basic `git` features. The video below shows how to use 46 | this TUI to: 47 | 48 | - list branches 49 | - create and checkout a new `demo` branch 50 | - add a file and create a commit 51 | - check the git log to validate that the commit has been created 52 | - delete the `demo` branch 53 | 54 | https://github.com/user-attachments/assets/5e1a5f59-d0e0-4e43-9737-302b99e62cfa 55 | 56 | Play around with this in a git repo! 57 | 58 | ### Basic Navigation 59 | 60 | Yakari is an interactive command menu that makes it easy to run complex 61 | commands. Think of it as a command launcher where you type shortcuts instead of 62 | remembering full commands. 63 | 64 | #### Start Typing 65 | 66 | When you launch Yakari, you'll see a menu of available options. Every menu item has a shortcut (shown on the left). Type the shortcut to select it: 67 | - Commands (things you can run) 68 | - Arguments (options you can set) 69 | - Submenus (more options inside) 70 | 71 | Other important keyboard shortcuts let you interact with the TUI: 72 | 73 | | Key | Action | 74 | |-------------|--------------------------| 75 | | ctrl+q | Cancel/Exit | 76 | | backspace | Erase/Go back | 77 | | tab | Auto-complete | 78 | | ctrl+r | Toggle results | 79 | | ctrl+l | Clear results | 80 | | ctrl+e | Toggle edit mode | 81 | 82 | > ![screenshot illustrating how yakari highlights compatible commands based on user's input](https://github.com/user-attachments/assets/95489bcd-832a-488b-b4eb-e75b5bcb30ec) 83 | > **Example**: In the git branch menu, typing `-` highlights the `-f` and `-t` arguments, and dims other entries. 84 | 85 | #### Working with Arguments 86 | 87 | Arguments are options you can set. There are two modes for handling them: 88 | 89 | Normal Mode (default): 90 | - Selecting an argument toggles it on/off 91 | - Great for quick switches like `--verbose` 92 | 93 | Edit Mode (press `ctrl+e` to switch): 94 | - Selecting an argument lets you edit its value 95 | - Perfect for editing named argument with an existing value 96 | 97 | 98 | ### (Optional) Installation 99 | 100 | Yakari is published to PyPI: 101 | 102 | ```bash 103 | # Using pip 104 | pip install yakari 105 | 106 | # Using uv 107 | uv tool install yakari 108 | ``` 109 | 110 | 111 | ## Menus 112 | 113 | A command menu is a TOML configuration file that defines a hierarchical 114 | interface for executing commands. It allows you to: 115 | - Organize related commands into menus 116 | - Define reusable arguments 117 | - Create interactive prompts for command values 118 | 119 | Install new menus by copying the corresponding TOML file into 120 | `$HOME/.config/yakari/menus`. If you're interested in creating your own menus, 121 | take a look at dedicated [readme from 122 | yakari-menus](https://github.com/vlandeiro/yakari-menus/blob/main/README.md). 123 | 124 | > [!TIP] 125 | > Yakari comes with a few [pre-defined menus](https://github.com/vlandeiro/yakari-menus) 126 | > that you can use directly via `ykr ` (e.g. `ykr git`). 127 | 128 | ## Features 129 | 130 | - Interactive command building 131 | - Contextual help and descriptions 132 | - Works alongside existing CLI tools 133 | - Command history across executions 134 | - Static and dynamic suggestions 135 | - In-place command execution with streamed output and support for interactive commands 136 | - Supported argument types: 137 | - Flag argument 138 | - Single-value argument 139 | - Multi-choice argument 140 | - Password argument 141 | - Multi-value argument 142 | 143 | ### Roadmap 144 | 145 | - Add argument types: 146 | - File argument 147 | - Support environment variables 148 | 149 | ## References 150 | 151 | - **Heavily** inspired by [transient](https://github.com/magit/transient). 152 | - Powered by: 153 | - [textual](https://github.com/Textualize/textual) 154 | - [pydantic](https://github.com/pydantic/pydantic) 155 | 156 | ### Why Yakari? 157 | 158 | The name comes from a Swiss comic book character who can talk to animals. 159 | Similarly, this tool helps users communicate more naturally with command-line 160 | programs by turning intimidating command structures into guided menus. 161 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "yakari" 3 | version = "0.1.0" 4 | description = "Interactive command building tool." 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "pydantic>=2.9.2", 9 | "rich>=13.9.4", 10 | "textual>=0.86.1", 11 | "tomlkit>=0.13.2", 12 | ] 13 | 14 | [project.scripts] 15 | ykr = "yakari.cli:main" 16 | 17 | [dependency-groups] 18 | dev = [ 19 | "pytest-asyncio>=0.24.0", 20 | "pytest-cov>=6.0.0", 21 | "pytest>=8.3.3", 22 | "python-lsp-ruff>=2.2.2", 23 | "python-lsp-server[all]>=1.12.0", 24 | "ruff>=0.7.4", 25 | "textual-dev>=1.6.1", 26 | ] 27 | 28 | [build-system] 29 | requires = ["hatchling"] 30 | build-backend = "hatchling.build" 31 | -------------------------------------------------------------------------------- /static/yakari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlandeiro/yakari/0e602f6fbc04a470baa4a63c942563ff1bd2713c/static/yakari.png -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlandeiro/yakari/0e602f6fbc04a470baa4a63c942563ff1bd2713c/test/__init__.py -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pathlib import Path 3 | from yakari.types import Menu 4 | from yakari.app import YakariApp 5 | 6 | 7 | @pytest.fixture 8 | def demo_app(): 9 | """Fixture providing a configured YakariApp instance.""" 10 | # Get the absolute path to the demos.toml file 11 | yakari_root = Path(__file__).parent.parent 12 | demo_file = yakari_root / "menus" / "demo.toml" 13 | 14 | menu = Menu.from_toml(demo_file) 15 | app = YakariApp(menu, dry_run=True) 16 | return app 17 | -------------------------------------------------------------------------------- /test/test_app.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.asyncio 5 | async def test_flag_argument(demo_app): 6 | async with demo_app.run_test() as pilot: 7 | # await pilot.pause() 8 | 9 | # Test flag argument 10 | await pilot.press("a") 11 | await pilot.press(*"-f") 12 | await pilot.press("d") 13 | 14 | assert demo_app.command == [ 15 | "echo", 16 | "--parent=100", 17 | "--flag", 18 | "--named-with-default=3", 19 | ] 20 | 21 | 22 | @pytest.mark.asyncio 23 | async def test_single_value_named_argument(demo_app): 24 | async with demo_app.run_test() as pilot: 25 | await pilot.press("a") 26 | await pilot.press(*"-n") 27 | await pilot.press(*"foo") 28 | await pilot.press("enter") 29 | await pilot.press("d") 30 | 31 | assert demo_app.command == [ 32 | "echo", 33 | "--parent=100", 34 | "--named=foo", 35 | "--named-with-default=3", 36 | ] 37 | 38 | 39 | @pytest.mark.asyncio 40 | async def test_multiple_values_named_argument(demo_app): 41 | async with demo_app.run_test() as pilot: 42 | # go to the arguments menu 43 | await pilot.press("a") 44 | 45 | await pilot.press(*"--mn") 46 | # type foo 47 | await pilot.press(*"foo") 48 | await pilot.press("enter") 49 | # type bar 50 | await pilot.press(*"bar") 51 | await pilot.press("enter") 52 | # select the last suggestion 53 | await pilot.press("shift+tab") 54 | await pilot.press("up") 55 | await pilot.press("enter") 56 | await pilot.press("enter") 57 | 58 | # submit the selected values 59 | await pilot.press("enter") 60 | # run the echo command 61 | await pilot.press("d") 62 | 63 | assert demo_app.command == [ 64 | "echo", 65 | "--parent=100", 66 | "--named-with-default=3", 67 | "--multi-named=foo", 68 | "--multi-named=bar", 69 | "--multi-named=peach", 70 | ] 71 | 72 | 73 | @pytest.mark.asyncio 74 | async def test_single_choice_argument(demo_app): 75 | async with demo_app.run_test() as pilot: 76 | await pilot.press("a") 77 | await pilot.press(*"-c") 78 | await pilot.press("down") 79 | await pilot.press("enter") 80 | await pilot.press("d") 81 | 82 | assert demo_app.command == [ 83 | "echo", 84 | "--parent=100", 85 | "--named-with-default=3", 86 | "--single-choice=rust", 87 | ] 88 | 89 | 90 | @pytest.mark.asyncio 91 | async def test_multi_choice_argument(demo_app): 92 | async with demo_app.run_test() as pilot: 93 | await pilot.press("a") 94 | await pilot.press(*"--mc") 95 | # select "jazz" 96 | await pilot.press("space") 97 | # select "npr news" 98 | await pilot.press("down", "down", "space") 99 | 100 | await pilot.press("enter") 101 | await pilot.press("d") 102 | 103 | assert demo_app.command == [ 104 | "echo", 105 | "--parent=100", 106 | "--named-with-default=3", 107 | "--multi-choice=jazz", 108 | "--multi-choice=npr news", 109 | ] 110 | -------------------------------------------------------------------------------- /test/test_types.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from yakari.types import SuggestionsList, SuggestionsCommand 3 | from unittest.mock import patch 4 | 5 | 6 | def test_suggestion_list(): 7 | sl = SuggestionsList(values=["item1", "item2"]) 8 | assert sl.values == ["item1", "item2"] 9 | 10 | 11 | class TestSuggestionsCommand: 12 | @pytest.fixture 13 | def command_instance(self): 14 | return SuggestionsCommand(command="echo test") 15 | 16 | @patch("subprocess.run") 17 | def test_suggestions_with_caching(self, mock_run, command_instance): 18 | command_instance.cache = True 19 | mock_run.return_value.stdout = b"line1\nline2\n" 20 | mock_run.return_value.stderr = b"" 21 | 22 | # First call should trigger subprocess 23 | assert command_instance.values == ["line1", "line2"] 24 | mock_run.assert_called_once() 25 | 26 | # Second call should use cached value 27 | assert command_instance.values == ["line1", "line2"] 28 | mock_run.assert_called_once() 29 | 30 | @patch("subprocess.run") 31 | def test_suggestions_without_caching(self, mock_run, command_instance): 32 | mock_run.return_value.stdout = b"line1\nline2\n" 33 | mock_run.return_value.stderr = b"" 34 | command_instance.cache = False 35 | 36 | # Each call should trigger subprocess 37 | assert command_instance.values == ["line1", "line2"] 38 | assert command_instance.values == ["line1", "line2"] 39 | assert mock_run.call_count == 2 40 | 41 | @patch("subprocess.run") 42 | def test_suggestions_with_error(self, mock_run, command_instance): 43 | mock_run.return_value.stderr = b"error message" 44 | 45 | with pytest.raises(RuntimeError) as exc_info: 46 | _ = command_instance.values 47 | assert "failed with the following message" in str(exc_info.value) 48 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | 4 | [[package]] 5 | name = "aiohappyeyeballs" 6 | version = "2.4.4" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/7f/55/e4373e888fdacb15563ef6fa9fa8c8252476ea071e96fb46defac9f18bf2/aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745", size = 21977 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/b9/74/fbb6559de3607b3300b9be3cc64e97548d55678e44623db17820dbd20002/aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8", size = 14756 }, 11 | ] 12 | 13 | [[package]] 14 | name = "aiohttp" 15 | version = "3.11.10" 16 | source = { registry = "https://pypi.org/simple" } 17 | dependencies = [ 18 | { name = "aiohappyeyeballs" }, 19 | { name = "aiosignal" }, 20 | { name = "attrs" }, 21 | { name = "frozenlist" }, 22 | { name = "multidict" }, 23 | { name = "propcache" }, 24 | { name = "yarl" }, 25 | ] 26 | sdist = { url = "https://files.pythonhosted.org/packages/94/c4/3b5a937b16f6c2a0ada842a9066aad0b7a5708427d4a202a07bf09c67cbb/aiohttp-3.11.10.tar.gz", hash = "sha256:b1fc6b45010a8d0ff9e88f9f2418c6fd408c99c211257334aff41597ebece42e", size = 7668832 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/25/17/1dbe2f619f77795409c1a13ab395b98ed1b215d3e938cacde9b8ffdac53d/aiohttp-3.11.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b78f053a7ecfc35f0451d961dacdc671f4bcbc2f58241a7c820e9d82559844cf", size = 704448 }, 29 | { url = "https://files.pythonhosted.org/packages/e3/9b/112247ad47e9d7f6640889c6e42cc0ded8c8345dd0033c66bcede799b051/aiohttp-3.11.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab7485222db0959a87fbe8125e233b5a6f01f4400785b36e8a7878170d8c3138", size = 463829 }, 30 | { url = "https://files.pythonhosted.org/packages/8a/36/a64b583771fc673062a7a1374728a6241d49e2eda5a9041fbf248e18c804/aiohttp-3.11.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf14627232dfa8730453752e9cdc210966490992234d77ff90bc8dc0dce361d5", size = 455774 }, 31 | { url = "https://files.pythonhosted.org/packages/e5/75/ee1b8f510978b3de5f185c62535b135e4fc3f5a247ca0c2245137a02d800/aiohttp-3.11.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076bc454a7e6fd646bc82ea7f98296be0b1219b5e3ef8a488afbdd8e81fbac50", size = 1682134 }, 32 | { url = "https://files.pythonhosted.org/packages/87/46/65e8259432d5f73ca9ebf5edb645ef90e5303724e4e52477516cb4042240/aiohttp-3.11.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:482cafb7dc886bebeb6c9ba7925e03591a62ab34298ee70d3dd47ba966370d2c", size = 1736757 }, 33 | { url = "https://files.pythonhosted.org/packages/03/f6/a6d1e791b7153fb2d101278f7146c0771b0e1569c547f8a8bc3035651984/aiohttp-3.11.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf3d1a519a324af764a46da4115bdbd566b3c73fb793ffb97f9111dbc684fc4d", size = 1793033 }, 34 | { url = "https://files.pythonhosted.org/packages/a8/e9/1ac90733e36e7848693aece522936a13bf17eeb617da662f94adfafc1c25/aiohttp-3.11.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24213ba85a419103e641e55c27dc7ff03536c4873470c2478cce3311ba1eee7b", size = 1691609 }, 35 | { url = "https://files.pythonhosted.org/packages/6d/a6/77b33da5a0bc04566c7ddcca94500f2c2a2334eecab4885387fffd1fc600/aiohttp-3.11.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b99acd4730ad1b196bfb03ee0803e4adac371ae8efa7e1cbc820200fc5ded109", size = 1619082 }, 36 | { url = "https://files.pythonhosted.org/packages/48/94/5bf5f927d9a2fedd2c978adfb70a3680e16f46d178361685b56244eb52ed/aiohttp-3.11.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:14cdb5a9570be5a04eec2ace174a48ae85833c2aadc86de68f55541f66ce42ab", size = 1641186 }, 37 | { url = "https://files.pythonhosted.org/packages/99/2d/e85103aa01d1064e51bc50cb51e7b40150a8ff5d34e5a3173a46b241860b/aiohttp-3.11.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7e97d622cb083e86f18317282084bc9fbf261801b0192c34fe4b1febd9f7ae69", size = 1646280 }, 38 | { url = "https://files.pythonhosted.org/packages/7b/e0/44651fda8c1d865a51b3a81f1956ea55ce16fc568fe7a3e05db7fc22f139/aiohttp-3.11.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:012f176945af138abc10c4a48743327a92b4ca9adc7a0e078077cdb5dbab7be0", size = 1701862 }, 39 | { url = "https://files.pythonhosted.org/packages/4e/1e/0804459ae325a5b95f6f349778fb465f29d2b863e522b6a349db0aaad54c/aiohttp-3.11.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44224d815853962f48fe124748227773acd9686eba6dc102578defd6fc99e8d9", size = 1734373 }, 40 | { url = "https://files.pythonhosted.org/packages/07/87/b8f6721668cad74bcc9c7cfe6d0230b304d1250196b221e54294a0d78dbe/aiohttp-3.11.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c87bf31b7fdab94ae3adbe4a48e711bfc5f89d21cf4c197e75561def39e223bc", size = 1694343 }, 41 | { url = "https://files.pythonhosted.org/packages/4b/20/42813fc60d9178ba9b1b86c58a5441ddb6cf8ffdfe66387345bff173bcff/aiohttp-3.11.10-cp312-cp312-win32.whl", hash = "sha256:06a8e2ee1cbac16fe61e51e0b0c269400e781b13bcfc33f5425912391a542985", size = 411118 }, 42 | { url = "https://files.pythonhosted.org/packages/3a/51/df9c263c861ce93998b5ad2ba3212caab2112d5b66dbe91ddbe90c41ded4/aiohttp-3.11.10-cp312-cp312-win_amd64.whl", hash = "sha256:be2b516f56ea883a3e14dda17059716593526e10fb6303189aaf5503937db408", size = 437424 }, 43 | { url = "https://files.pythonhosted.org/packages/8c/1d/88bfdbe28a3d1ba5b94a235f188f27726caf8ade9a0e13574848f44fe0fe/aiohttp-3.11.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8cc5203b817b748adccb07f36390feb730b1bc5f56683445bfe924fc270b8816", size = 697755 }, 44 | { url = "https://files.pythonhosted.org/packages/86/00/4c4619d6fe5c5be32f74d1422fc719b3e6cd7097af0c9e03877ca9bd4ebc/aiohttp-3.11.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ef359ebc6949e3a34c65ce20230fae70920714367c63afd80ea0c2702902ccf", size = 460440 }, 45 | { url = "https://files.pythonhosted.org/packages/aa/1c/2f927408f50593a29465d198ec3c57c835c8602330233163e8d89c1093db/aiohttp-3.11.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9bca390cb247dbfaec3c664326e034ef23882c3f3bfa5fbf0b56cad0320aaca5", size = 452726 }, 46 | { url = "https://files.pythonhosted.org/packages/06/6a/ff00ed0a2ba45c34b3c366aa5b0004b1a4adcec5a9b5f67dd0648ee1c88a/aiohttp-3.11.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811f23b3351ca532af598405db1093f018edf81368e689d1b508c57dcc6b6a32", size = 1664944 }, 47 | { url = "https://files.pythonhosted.org/packages/02/c2/61923f2a7c2e14d7424b3a526e054f0358f57ccdf5573d4d3d033b01921a/aiohttp-3.11.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddf5f7d877615f6a1e75971bfa5ac88609af3b74796ff3e06879e8422729fd01", size = 1717707 }, 48 | { url = "https://files.pythonhosted.org/packages/8a/08/0d3d074b24d377569ec89d476a95ca918443099c0401bb31b331104e35d1/aiohttp-3.11.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ab29b8a0beb6f8eaf1e5049252cfe74adbaafd39ba91e10f18caeb0e99ffb34", size = 1774890 }, 49 | { url = "https://files.pythonhosted.org/packages/e8/49/052ada2b6e90ed65f0e6a7e548614621b5f8dcd193cb9415d2e6bcecc94a/aiohttp-3.11.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c49a76c1038c2dd116fa443eba26bbb8e6c37e924e2513574856de3b6516be99", size = 1676945 }, 50 | { url = "https://files.pythonhosted.org/packages/7c/9e/0c48e1a48e072a869b8b5e3920c9f6a8092861524a4a6f159cd7e6fda939/aiohttp-3.11.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f3dc0e330575f5b134918976a645e79adf333c0a1439dcf6899a80776c9ab39", size = 1602959 }, 51 | { url = "https://files.pythonhosted.org/packages/ab/98/791f979093ff7f67f80344c182cb0ca4c2c60daed397ecaf454cc8d7a5cd/aiohttp-3.11.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:efb15a17a12497685304b2d976cb4939e55137df7b09fa53f1b6a023f01fcb4e", size = 1618058 }, 52 | { url = "https://files.pythonhosted.org/packages/7b/5d/2d4b05feb3fd68eb7c8335f73c81079b56e582633b91002da695ccb439ef/aiohttp-3.11.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:db1d0b28fcb7f1d35600150c3e4b490775251dea70f894bf15c678fdd84eda6a", size = 1616289 }, 53 | { url = "https://files.pythonhosted.org/packages/50/83/68cc28c00fe681dce6150614f105efe98282da19252cd6e32dfa893bb328/aiohttp-3.11.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:15fccaf62a4889527539ecb86834084ecf6e9ea70588efde86e8bc775e0e7542", size = 1685239 }, 54 | { url = "https://files.pythonhosted.org/packages/16/f9/68fc5c8928f63238ce9314f04f3f59d9190a4db924998bb9be99c7aacce8/aiohttp-3.11.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:593c114a2221444f30749cc5e5f4012488f56bd14de2af44fe23e1e9894a9c60", size = 1715078 }, 55 | { url = "https://files.pythonhosted.org/packages/3f/e0/3dd3f0451c532c77e35780bafb2b6469a046bc15a6ec2e039475a1d2f161/aiohttp-3.11.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7852bbcb4d0d2f0c4d583f40c3bc750ee033265d80598d0f9cb6f372baa6b836", size = 1672544 }, 56 | { url = "https://files.pythonhosted.org/packages/a5/b1/3530ab040dd5d7fb016b47115016f9b3a07ea29593b0e07e53dbe06a380c/aiohttp-3.11.10-cp313-cp313-win32.whl", hash = "sha256:65e55ca7debae8faaffee0ebb4b47a51b4075f01e9b641c31e554fd376595c6c", size = 409984 }, 57 | { url = "https://files.pythonhosted.org/packages/49/1f/deed34e9fca639a7f873d01150d46925d3e1312051eaa591c1aa1f2e6ddc/aiohttp-3.11.10-cp313-cp313-win_amd64.whl", hash = "sha256:beb39a6d60a709ae3fb3516a1581777e7e8b76933bb88c8f4420d875bb0267c6", size = 435837 }, 58 | ] 59 | 60 | [[package]] 61 | name = "aiohttp-jinja2" 62 | version = "1.6" 63 | source = { registry = "https://pypi.org/simple" } 64 | dependencies = [ 65 | { name = "aiohttp" }, 66 | { name = "jinja2" }, 67 | ] 68 | sdist = { url = "https://files.pythonhosted.org/packages/e6/39/da5a94dd89b1af7241fb7fc99ae4e73505b5f898b540b6aba6dc7afe600e/aiohttp-jinja2-1.6.tar.gz", hash = "sha256:a3a7ff5264e5bca52e8ae547bbfd0761b72495230d438d05b6c0915be619b0e2", size = 53057 } 69 | wheels = [ 70 | { url = "https://files.pythonhosted.org/packages/eb/90/65238d4246307195411b87a07d03539049819b022c01bcc773826f600138/aiohttp_jinja2-1.6-py3-none-any.whl", hash = "sha256:0df405ee6ad1b58e5a068a105407dc7dcc1704544c559f1938babde954f945c7", size = 11736 }, 71 | ] 72 | 73 | [[package]] 74 | name = "aiosignal" 75 | version = "1.3.1" 76 | source = { registry = "https://pypi.org/simple" } 77 | dependencies = [ 78 | { name = "frozenlist" }, 79 | ] 80 | sdist = { url = "https://files.pythonhosted.org/packages/ae/67/0952ed97a9793b4958e5736f6d2b346b414a2cd63e82d05940032f45b32f/aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", size = 19422 } 81 | wheels = [ 82 | { url = "https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17", size = 7617 }, 83 | ] 84 | 85 | [[package]] 86 | name = "annotated-types" 87 | version = "0.7.0" 88 | source = { registry = "https://pypi.org/simple" } 89 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 90 | wheels = [ 91 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 92 | ] 93 | 94 | [[package]] 95 | name = "astroid" 96 | version = "3.3.6" 97 | source = { registry = "https://pypi.org/simple" } 98 | sdist = { url = "https://files.pythonhosted.org/packages/ca/40/e028137cb19ed577001c76b91c5c50fee5a9c85099f45820b69385574ac5/astroid-3.3.6.tar.gz", hash = "sha256:6aaea045f938c735ead292204afdb977a36e989522b7833ef6fea94de743f442", size = 397452 } 99 | wheels = [ 100 | { url = "https://files.pythonhosted.org/packages/0c/d2/82c8ccef22ea873a2b0da9636e47d45137eeeb2fb9320c5dbbdd3627bab0/astroid-3.3.6-py3-none-any.whl", hash = "sha256:db676dc4f3ae6bfe31cda227dc60e03438378d7a896aec57422c95634e8d722f", size = 274644 }, 101 | ] 102 | 103 | [[package]] 104 | name = "attrs" 105 | version = "24.2.0" 106 | source = { registry = "https://pypi.org/simple" } 107 | sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } 108 | wheels = [ 109 | { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, 110 | ] 111 | 112 | [[package]] 113 | name = "autopep8" 114 | version = "2.0.4" 115 | source = { registry = "https://pypi.org/simple" } 116 | dependencies = [ 117 | { name = "pycodestyle" }, 118 | ] 119 | sdist = { url = "https://files.pythonhosted.org/packages/e0/8a/9be661f5400867a09706e29f5ab99a59987fd3a4c337757365e7491fa90b/autopep8-2.0.4.tar.gz", hash = "sha256:2913064abd97b3419d1cc83ea71f042cb821f87e45b9c88cad5ad3c4ea87fe0c", size = 116472 } 120 | wheels = [ 121 | { url = "https://files.pythonhosted.org/packages/d8/f2/e63c9f9c485cd90df8e4e7ae90fa3be2469c9641888558c7b45fa98a76f8/autopep8-2.0.4-py2.py3-none-any.whl", hash = "sha256:067959ca4a07b24dbd5345efa8325f5f58da4298dab0dde0443d5ed765de80cb", size = 45340 }, 122 | ] 123 | 124 | [[package]] 125 | name = "cattrs" 126 | version = "24.1.2" 127 | source = { registry = "https://pypi.org/simple" } 128 | dependencies = [ 129 | { name = "attrs" }, 130 | ] 131 | sdist = { url = "https://files.pythonhosted.org/packages/64/65/af6d57da2cb32c076319b7489ae0958f746949d407109e3ccf4d115f147c/cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85", size = 426462 } 132 | wheels = [ 133 | { url = "https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0", size = 66446 }, 134 | ] 135 | 136 | [[package]] 137 | name = "click" 138 | version = "8.1.7" 139 | source = { registry = "https://pypi.org/simple" } 140 | dependencies = [ 141 | { name = "colorama", marker = "platform_system == 'Windows'" }, 142 | ] 143 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } 144 | wheels = [ 145 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, 146 | ] 147 | 148 | [[package]] 149 | name = "colorama" 150 | version = "0.4.6" 151 | source = { registry = "https://pypi.org/simple" } 152 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 153 | wheels = [ 154 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 155 | ] 156 | 157 | [[package]] 158 | name = "coverage" 159 | version = "7.6.9" 160 | source = { registry = "https://pypi.org/simple" } 161 | sdist = { url = "https://files.pythonhosted.org/packages/5b/d2/c25011f4d036cf7e8acbbee07a8e09e9018390aee25ba085596c4b83d510/coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", size = 801710 } 162 | wheels = [ 163 | { url = "https://files.pythonhosted.org/packages/60/52/b16af8989a2daf0f80a88522bd8e8eed90b5fcbdecf02a6888f3e80f6ba7/coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", size = 207325 }, 164 | { url = "https://files.pythonhosted.org/packages/0f/79/6b7826fca8846c1216a113227b9f114ac3e6eacf168b4adcad0cb974aaca/coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", size = 207563 }, 165 | { url = "https://files.pythonhosted.org/packages/a7/07/0bc73da0ccaf45d0d64ef86d33b7d7fdeef84b4c44bf6b85fb12c215c5a6/coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", size = 240580 }, 166 | { url = "https://files.pythonhosted.org/packages/71/8a/9761f409910961647d892454687cedbaccb99aae828f49486734a82ede6e/coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3", size = 237613 }, 167 | { url = "https://files.pythonhosted.org/packages/8b/10/ee7d696a17ac94f32f2dbda1e17e730bf798ae9931aec1fc01c1944cd4de/coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", size = 239684 }, 168 | { url = "https://files.pythonhosted.org/packages/16/60/aa1066040d3c52fff051243c2d6ccda264da72dc6d199d047624d395b2b2/coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", size = 239112 }, 169 | { url = "https://files.pythonhosted.org/packages/4e/e5/69f35344c6f932ba9028bf168d14a79fedb0dd4849b796d43c81ce75a3c9/coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", size = 237428 }, 170 | { url = "https://files.pythonhosted.org/packages/32/20/adc895523c4a28f63441b8ac645abd74f9bdd499d2d175bef5b41fc7f92d/coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", size = 239098 }, 171 | { url = "https://files.pythonhosted.org/packages/a9/a6/e0e74230c9bb3549ec8ffc137cfd16ea5d56e993d6bffed2218bff6187e3/coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", size = 209940 }, 172 | { url = "https://files.pythonhosted.org/packages/3e/18/cb5b88349d4aa2f41ec78d65f92ea32572b30b3f55bc2b70e87578b8f434/coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", size = 210726 }, 173 | { url = "https://files.pythonhosted.org/packages/35/26/9abab6539d2191dbda2ce8c97b67d74cbfc966cc5b25abb880ffc7c459bc/coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", size = 207356 }, 174 | { url = "https://files.pythonhosted.org/packages/44/da/d49f19402240c93453f606e660a6676a2a1fbbaa6870cc23207790aa9697/coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", size = 207614 }, 175 | { url = "https://files.pythonhosted.org/packages/da/e6/93bb9bf85497816082ec8da6124c25efa2052bd4c887dd3b317b91990c9e/coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", size = 240129 }, 176 | { url = "https://files.pythonhosted.org/packages/df/65/6a824b9406fe066835c1274a9949e06f084d3e605eb1a602727a27ec2fe3/coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", size = 237276 }, 177 | { url = "https://files.pythonhosted.org/packages/9f/79/6c7a800913a9dd23ac8c8da133ebb556771a5a3d4df36b46767b1baffd35/coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", size = 239267 }, 178 | { url = "https://files.pythonhosted.org/packages/57/e7/834d530293fdc8a63ba8ff70033d5182022e569eceb9aec7fc716b678a39/coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", size = 238887 }, 179 | { url = "https://files.pythonhosted.org/packages/15/05/ec9d6080852984f7163c96984444e7cd98b338fd045b191064f943ee1c08/coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", size = 236970 }, 180 | { url = "https://files.pythonhosted.org/packages/0a/d8/775937670b93156aec29f694ce37f56214ed7597e1a75b4083ee4c32121c/coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", size = 238831 }, 181 | { url = "https://files.pythonhosted.org/packages/f4/58/88551cb7fdd5ec98cb6044e8814e38583436b14040a5ece15349c44c8f7c/coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", size = 210000 }, 182 | { url = "https://files.pythonhosted.org/packages/b7/12/cfbf49b95120872785ff8d56ab1c7fe3970a65e35010c311d7dd35c5fd00/coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", size = 210753 }, 183 | { url = "https://files.pythonhosted.org/packages/7c/68/c1cb31445599b04bde21cbbaa6d21b47c5823cdfef99eae470dfce49c35a/coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", size = 208091 }, 184 | { url = "https://files.pythonhosted.org/packages/11/73/84b02c6b19c4a11eb2d5b5eabe926fb26c21c080e0852f5e5a4f01165f9e/coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", size = 208369 }, 185 | { url = "https://files.pythonhosted.org/packages/de/e0/ae5d878b72ff26df2e994a5c5b1c1f6a7507d976b23beecb1ed4c85411ef/coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", size = 251089 }, 186 | { url = "https://files.pythonhosted.org/packages/ab/9c/0aaac011aef95a93ef3cb2fba3fde30bc7e68a6635199ed469b1f5ea355a/coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", size = 246806 }, 187 | { url = "https://files.pythonhosted.org/packages/f8/19/4d5d3ae66938a7dcb2f58cef3fa5386f838f469575b0bb568c8cc9e3a33d/coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", size = 249164 }, 188 | { url = "https://files.pythonhosted.org/packages/b3/0b/4ee8a7821f682af9ad440ae3c1e379da89a998883271f088102d7ca2473d/coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", size = 248642 }, 189 | { url = "https://files.pythonhosted.org/packages/8a/12/36ff1d52be18a16b4700f561852e7afd8df56363a5edcfb04cf26a0e19e0/coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", size = 246516 }, 190 | { url = "https://files.pythonhosted.org/packages/43/d0/8e258f6c3a527c1655602f4f576215e055ac704de2d101710a71a2affac2/coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", size = 247783 }, 191 | { url = "https://files.pythonhosted.org/packages/a9/0d/1e4a48d289429d38aae3babdfcadbf35ca36bdcf3efc8f09b550a845bdb5/coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", size = 210646 }, 192 | { url = "https://files.pythonhosted.org/packages/26/74/b0729f196f328ac55e42b1e22ec2f16d8bcafe4b8158a26ec9f1cdd1d93e/coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", size = 211815 }, 193 | ] 194 | 195 | [[package]] 196 | name = "dill" 197 | version = "0.3.9" 198 | source = { registry = "https://pypi.org/simple" } 199 | sdist = { url = "https://files.pythonhosted.org/packages/70/43/86fe3f9e130c4137b0f1b50784dd70a5087b911fe07fa81e53e0c4c47fea/dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c", size = 187000 } 200 | wheels = [ 201 | { url = "https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a", size = 119418 }, 202 | ] 203 | 204 | [[package]] 205 | name = "docstring-to-markdown" 206 | version = "0.15" 207 | source = { registry = "https://pypi.org/simple" } 208 | sdist = { url = "https://files.pythonhosted.org/packages/7a/ad/6a66abd14676619bd56f6b924c96321a2e2d7d86558841d94a30023eec53/docstring-to-markdown-0.15.tar.gz", hash = "sha256:e146114d9c50c181b1d25505054a8d0f7a476837f0da2c19f07e06eaed52b73d", size = 29246 } 209 | wheels = [ 210 | { url = "https://files.pythonhosted.org/packages/c1/cf/4eee59f6c4111b3e80cc32cf6bac483a90646f5c8693e84496c9855e8e38/docstring_to_markdown-0.15-py3-none-any.whl", hash = "sha256:27afb3faedba81e34c33521c32bbd258d7fbb79eedf7d29bc4e81080e854aec0", size = 21640 }, 211 | ] 212 | 213 | [[package]] 214 | name = "flake8" 215 | version = "7.1.1" 216 | source = { registry = "https://pypi.org/simple" } 217 | dependencies = [ 218 | { name = "mccabe" }, 219 | { name = "pycodestyle" }, 220 | { name = "pyflakes" }, 221 | ] 222 | sdist = { url = "https://files.pythonhosted.org/packages/37/72/e8d66150c4fcace3c0a450466aa3480506ba2cae7b61e100a2613afc3907/flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", size = 48054 } 223 | wheels = [ 224 | { url = "https://files.pythonhosted.org/packages/d9/42/65004373ac4617464f35ed15931b30d764f53cdd30cc78d5aea349c8c050/flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213", size = 57731 }, 225 | ] 226 | 227 | [[package]] 228 | name = "frozenlist" 229 | version = "1.5.0" 230 | source = { registry = "https://pypi.org/simple" } 231 | sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } 232 | wheels = [ 233 | { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, 234 | { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, 235 | { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, 236 | { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, 237 | { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, 238 | { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, 239 | { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, 240 | { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, 241 | { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, 242 | { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, 243 | { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, 244 | { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, 245 | { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, 246 | { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, 247 | { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, 248 | { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, 249 | { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, 250 | { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, 251 | { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, 252 | { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, 253 | { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, 254 | { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, 255 | { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, 256 | { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, 257 | { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, 258 | { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, 259 | { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, 260 | { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, 261 | { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, 262 | { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, 263 | { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, 264 | ] 265 | 266 | [[package]] 267 | name = "idna" 268 | version = "3.10" 269 | source = { registry = "https://pypi.org/simple" } 270 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 271 | wheels = [ 272 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 273 | ] 274 | 275 | [[package]] 276 | name = "iniconfig" 277 | version = "2.0.0" 278 | source = { registry = "https://pypi.org/simple" } 279 | sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } 280 | wheels = [ 281 | { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, 282 | ] 283 | 284 | [[package]] 285 | name = "isort" 286 | version = "5.13.2" 287 | source = { registry = "https://pypi.org/simple" } 288 | sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } 289 | wheels = [ 290 | { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, 291 | ] 292 | 293 | [[package]] 294 | name = "jedi" 295 | version = "0.19.2" 296 | source = { registry = "https://pypi.org/simple" } 297 | dependencies = [ 298 | { name = "parso" }, 299 | ] 300 | sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } 301 | wheels = [ 302 | { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, 303 | ] 304 | 305 | [[package]] 306 | name = "jinja2" 307 | version = "3.1.4" 308 | source = { registry = "https://pypi.org/simple" } 309 | dependencies = [ 310 | { name = "markupsafe" }, 311 | ] 312 | sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } 313 | wheels = [ 314 | { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, 315 | ] 316 | 317 | [[package]] 318 | name = "linkify-it-py" 319 | version = "2.0.3" 320 | source = { registry = "https://pypi.org/simple" } 321 | dependencies = [ 322 | { name = "uc-micro-py" }, 323 | ] 324 | sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946 } 325 | wheels = [ 326 | { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820 }, 327 | ] 328 | 329 | [[package]] 330 | name = "lsprotocol" 331 | version = "2023.0.1" 332 | source = { registry = "https://pypi.org/simple" } 333 | dependencies = [ 334 | { name = "attrs" }, 335 | { name = "cattrs" }, 336 | ] 337 | sdist = { url = "https://files.pythonhosted.org/packages/9d/f6/6e80484ec078d0b50699ceb1833597b792a6c695f90c645fbaf54b947e6f/lsprotocol-2023.0.1.tar.gz", hash = "sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d", size = 69434 } 338 | wheels = [ 339 | { url = "https://files.pythonhosted.org/packages/8d/37/2351e48cb3309673492d3a8c59d407b75fb6630e560eb27ecd4da03adc9a/lsprotocol-2023.0.1-py3-none-any.whl", hash = "sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2", size = 70826 }, 340 | ] 341 | 342 | [[package]] 343 | name = "markdown-it-py" 344 | version = "3.0.0" 345 | source = { registry = "https://pypi.org/simple" } 346 | dependencies = [ 347 | { name = "mdurl" }, 348 | ] 349 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 350 | wheels = [ 351 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 352 | ] 353 | 354 | [package.optional-dependencies] 355 | linkify = [ 356 | { name = "linkify-it-py" }, 357 | ] 358 | plugins = [ 359 | { name = "mdit-py-plugins" }, 360 | ] 361 | 362 | [[package]] 363 | name = "markupsafe" 364 | version = "3.0.2" 365 | source = { registry = "https://pypi.org/simple" } 366 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 367 | wheels = [ 368 | { 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 }, 369 | { 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 }, 370 | { 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 }, 371 | { 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 }, 372 | { 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 }, 373 | { 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 }, 374 | { 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 }, 375 | { 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 }, 376 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, 377 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, 378 | { 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 }, 379 | { 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 }, 380 | { 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 }, 381 | { 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 }, 382 | { 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 }, 383 | { 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 }, 384 | { 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 }, 385 | { 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 }, 386 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, 387 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, 388 | { 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 }, 389 | { 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 }, 390 | { 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 }, 391 | { 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 }, 392 | { 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 }, 393 | { 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 }, 394 | { 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 }, 395 | { 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 }, 396 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, 397 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, 398 | ] 399 | 400 | [[package]] 401 | name = "mccabe" 402 | version = "0.7.0" 403 | source = { registry = "https://pypi.org/simple" } 404 | sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } 405 | wheels = [ 406 | { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, 407 | ] 408 | 409 | [[package]] 410 | name = "mdit-py-plugins" 411 | version = "0.4.2" 412 | source = { registry = "https://pypi.org/simple" } 413 | dependencies = [ 414 | { name = "markdown-it-py" }, 415 | ] 416 | sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } 417 | wheels = [ 418 | { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, 419 | ] 420 | 421 | [[package]] 422 | name = "mdurl" 423 | version = "0.1.2" 424 | source = { registry = "https://pypi.org/simple" } 425 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 426 | wheels = [ 427 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 428 | ] 429 | 430 | [[package]] 431 | name = "msgpack" 432 | version = "1.1.0" 433 | source = { registry = "https://pypi.org/simple" } 434 | sdist = { url = "https://files.pythonhosted.org/packages/cb/d0/7555686ae7ff5731205df1012ede15dd9d927f6227ea151e901c7406af4f/msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", size = 167260 } 435 | wheels = [ 436 | { url = "https://files.pythonhosted.org/packages/e1/d6/716b7ca1dbde63290d2973d22bbef1b5032ca634c3ff4384a958ec3f093a/msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d", size = 152421 }, 437 | { url = "https://files.pythonhosted.org/packages/70/da/5312b067f6773429cec2f8f08b021c06af416bba340c912c2ec778539ed6/msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2", size = 85277 }, 438 | { url = "https://files.pythonhosted.org/packages/28/51/da7f3ae4462e8bb98af0d5bdf2707f1b8c65a0d4f496e46b6afb06cbc286/msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420", size = 82222 }, 439 | { url = "https://files.pythonhosted.org/packages/33/af/dc95c4b2a49cff17ce47611ca9ba218198806cad7796c0b01d1e332c86bb/msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2", size = 392971 }, 440 | { url = "https://files.pythonhosted.org/packages/f1/54/65af8de681fa8255402c80eda2a501ba467921d5a7a028c9c22a2c2eedb5/msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39", size = 401403 }, 441 | { url = "https://files.pythonhosted.org/packages/97/8c/e333690777bd33919ab7024269dc3c41c76ef5137b211d776fbb404bfead/msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f", size = 385356 }, 442 | { url = "https://files.pythonhosted.org/packages/57/52/406795ba478dc1c890559dd4e89280fa86506608a28ccf3a72fbf45df9f5/msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247", size = 383028 }, 443 | { url = "https://files.pythonhosted.org/packages/e7/69/053b6549bf90a3acadcd8232eae03e2fefc87f066a5b9fbb37e2e608859f/msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c", size = 391100 }, 444 | { url = "https://files.pythonhosted.org/packages/23/f0/d4101d4da054f04274995ddc4086c2715d9b93111eb9ed49686c0f7ccc8a/msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b", size = 394254 }, 445 | { url = "https://files.pythonhosted.org/packages/1c/12/cf07458f35d0d775ff3a2dc5559fa2e1fcd06c46f1ef510e594ebefdca01/msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b", size = 69085 }, 446 | { url = "https://files.pythonhosted.org/packages/73/80/2708a4641f7d553a63bc934a3eb7214806b5b39d200133ca7f7afb0a53e8/msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", size = 75347 }, 447 | { url = "https://files.pythonhosted.org/packages/c8/b0/380f5f639543a4ac413e969109978feb1f3c66e931068f91ab6ab0f8be00/msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf", size = 151142 }, 448 | { url = "https://files.pythonhosted.org/packages/c8/ee/be57e9702400a6cb2606883d55b05784fada898dfc7fd12608ab1fdb054e/msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330", size = 84523 }, 449 | { url = "https://files.pythonhosted.org/packages/7e/3a/2919f63acca3c119565449681ad08a2f84b2171ddfcff1dba6959db2cceb/msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734", size = 81556 }, 450 | { url = "https://files.pythonhosted.org/packages/7c/43/a11113d9e5c1498c145a8925768ea2d5fce7cbab15c99cda655aa09947ed/msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e", size = 392105 }, 451 | { url = "https://files.pythonhosted.org/packages/2d/7b/2c1d74ca6c94f70a1add74a8393a0138172207dc5de6fc6269483519d048/msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca", size = 399979 }, 452 | { url = "https://files.pythonhosted.org/packages/82/8c/cf64ae518c7b8efc763ca1f1348a96f0e37150061e777a8ea5430b413a74/msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915", size = 383816 }, 453 | { url = "https://files.pythonhosted.org/packages/69/86/a847ef7a0f5ef3fa94ae20f52a4cacf596a4e4a010197fbcc27744eb9a83/msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d", size = 380973 }, 454 | { url = "https://files.pythonhosted.org/packages/aa/90/c74cf6e1126faa93185d3b830ee97246ecc4fe12cf9d2d31318ee4246994/msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434", size = 387435 }, 455 | { url = "https://files.pythonhosted.org/packages/7a/40/631c238f1f338eb09f4acb0f34ab5862c4e9d7eda11c1b685471a4c5ea37/msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c", size = 399082 }, 456 | { url = "https://files.pythonhosted.org/packages/e9/1b/fa8a952be252a1555ed39f97c06778e3aeb9123aa4cccc0fd2acd0b4e315/msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc", size = 69037 }, 457 | { url = "https://files.pythonhosted.org/packages/b6/bc/8bd826dd03e022153bfa1766dcdec4976d6c818865ed54223d71f07862b3/msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f", size = 75140 }, 458 | ] 459 | 460 | [[package]] 461 | name = "multidict" 462 | version = "6.1.0" 463 | source = { registry = "https://pypi.org/simple" } 464 | sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } 465 | wheels = [ 466 | { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, 467 | { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, 468 | { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, 469 | { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, 470 | { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, 471 | { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, 472 | { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, 473 | { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, 474 | { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, 475 | { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, 476 | { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, 477 | { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, 478 | { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, 479 | { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, 480 | { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, 481 | { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, 482 | { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, 483 | { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, 484 | { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, 485 | { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, 486 | { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, 487 | { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, 488 | { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, 489 | { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, 490 | { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, 491 | { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, 492 | { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, 493 | { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, 494 | { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, 495 | { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, 496 | { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, 497 | ] 498 | 499 | [[package]] 500 | name = "packaging" 501 | version = "24.2" 502 | source = { registry = "https://pypi.org/simple" } 503 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 504 | wheels = [ 505 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 506 | ] 507 | 508 | [[package]] 509 | name = "parso" 510 | version = "0.8.4" 511 | source = { registry = "https://pypi.org/simple" } 512 | sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } 513 | wheels = [ 514 | { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, 515 | ] 516 | 517 | [[package]] 518 | name = "platformdirs" 519 | version = "4.3.6" 520 | source = { registry = "https://pypi.org/simple" } 521 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } 522 | wheels = [ 523 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, 524 | ] 525 | 526 | [[package]] 527 | name = "pluggy" 528 | version = "1.5.0" 529 | source = { registry = "https://pypi.org/simple" } 530 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 531 | wheels = [ 532 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 533 | ] 534 | 535 | [[package]] 536 | name = "propcache" 537 | version = "0.2.1" 538 | source = { registry = "https://pypi.org/simple" } 539 | sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } 540 | wheels = [ 541 | { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, 542 | { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, 543 | { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, 544 | { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, 545 | { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, 546 | { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, 547 | { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, 548 | { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, 549 | { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, 550 | { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, 551 | { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, 552 | { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, 553 | { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, 554 | { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, 555 | { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, 556 | { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, 557 | { url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 }, 558 | { url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 }, 559 | { url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 }, 560 | { url = "https://files.pythonhosted.org/packages/4e/3e/021b6cd86c0acc90d74784ccbb66808b0bd36067a1bf3e2deb0f3845f618/propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536", size = 224819 }, 561 | { url = "https://files.pythonhosted.org/packages/3c/57/c2fdeed1b3b8918b1770a133ba5c43ad3d78e18285b0c06364861ef5cc38/propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629", size = 229625 }, 562 | { url = "https://files.pythonhosted.org/packages/9d/81/70d4ff57bf2877b5780b466471bebf5892f851a7e2ca0ae7ffd728220281/propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b", size = 232934 }, 563 | { url = "https://files.pythonhosted.org/packages/3c/b9/bb51ea95d73b3fb4100cb95adbd4e1acaf2cbb1fd1083f5468eeb4a099a8/propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052", size = 227361 }, 564 | { url = "https://files.pythonhosted.org/packages/f1/20/3c6d696cd6fd70b29445960cc803b1851a1131e7a2e4ee261ee48e002bcd/propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce", size = 213904 }, 565 | { url = "https://files.pythonhosted.org/packages/a1/cb/1593bfc5ac6d40c010fa823f128056d6bc25b667f5393781e37d62f12005/propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d", size = 212632 }, 566 | { url = "https://files.pythonhosted.org/packages/6d/5c/e95617e222be14a34c709442a0ec179f3207f8a2b900273720501a70ec5e/propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce", size = 207897 }, 567 | { url = "https://files.pythonhosted.org/packages/8e/3b/56c5ab3dc00f6375fbcdeefdede5adf9bee94f1fab04adc8db118f0f9e25/propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95", size = 208118 }, 568 | { url = "https://files.pythonhosted.org/packages/86/25/d7ef738323fbc6ebcbce33eb2a19c5e07a89a3df2fded206065bd5e868a9/propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf", size = 217851 }, 569 | { url = "https://files.pythonhosted.org/packages/b3/77/763e6cef1852cf1ba740590364ec50309b89d1c818e3256d3929eb92fabf/propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f", size = 222630 }, 570 | { url = "https://files.pythonhosted.org/packages/4f/e9/0f86be33602089c701696fbed8d8c4c07b6ee9605c5b7536fd27ed540c5b/propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30", size = 216269 }, 571 | { url = "https://files.pythonhosted.org/packages/cc/02/5ac83217d522394b6a2e81a2e888167e7ca629ef6569a3f09852d6dcb01a/propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6", size = 39472 }, 572 | { url = "https://files.pythonhosted.org/packages/f4/33/d6f5420252a36034bc8a3a01171bc55b4bff5df50d1c63d9caa50693662f/propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1", size = 43363 }, 573 | { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, 574 | ] 575 | 576 | [[package]] 577 | name = "pycodestyle" 578 | version = "2.12.1" 579 | source = { registry = "https://pypi.org/simple" } 580 | sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232 } 581 | wheels = [ 582 | { url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284 }, 583 | ] 584 | 585 | [[package]] 586 | name = "pydantic" 587 | version = "2.10.3" 588 | source = { registry = "https://pypi.org/simple" } 589 | dependencies = [ 590 | { name = "annotated-types" }, 591 | { name = "pydantic-core" }, 592 | { name = "typing-extensions" }, 593 | ] 594 | sdist = { url = "https://files.pythonhosted.org/packages/45/0f/27908242621b14e649a84e62b133de45f84c255eecb350ab02979844a788/pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9", size = 786486 } 595 | wheels = [ 596 | { url = "https://files.pythonhosted.org/packages/62/51/72c18c55cf2f46ff4f91ebcc8f75aa30f7305f3d726be3f4ebffb4ae972b/pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d", size = 456997 }, 597 | ] 598 | 599 | [[package]] 600 | name = "pydantic-core" 601 | version = "2.27.1" 602 | source = { registry = "https://pypi.org/simple" } 603 | dependencies = [ 604 | { name = "typing-extensions" }, 605 | ] 606 | sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 } 607 | wheels = [ 608 | { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 }, 609 | { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 }, 610 | { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 }, 611 | { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 }, 612 | { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 }, 613 | { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 }, 614 | { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 }, 615 | { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 }, 616 | { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 }, 617 | { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 }, 618 | { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 }, 619 | { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 }, 620 | { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 }, 621 | { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 }, 622 | { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 }, 623 | { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 }, 624 | { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 }, 625 | { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 }, 626 | { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 }, 627 | { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 }, 628 | { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 }, 629 | { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 }, 630 | { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 }, 631 | { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 }, 632 | { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 }, 633 | { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 }, 634 | { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 }, 635 | { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, 636 | ] 637 | 638 | [[package]] 639 | name = "pydocstyle" 640 | version = "6.3.0" 641 | source = { registry = "https://pypi.org/simple" } 642 | dependencies = [ 643 | { name = "snowballstemmer" }, 644 | ] 645 | sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/d5385ca59fd065e3c6a5fe19f9bc9d5ea7f2509fa8c9c22fb6b2031dd953/pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1", size = 36796 } 646 | wheels = [ 647 | { url = "https://files.pythonhosted.org/packages/36/ea/99ddefac41971acad68f14114f38261c1f27dac0b3ec529824ebc739bdaa/pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019", size = 38038 }, 648 | ] 649 | 650 | [[package]] 651 | name = "pyflakes" 652 | version = "3.2.0" 653 | source = { registry = "https://pypi.org/simple" } 654 | sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 } 655 | wheels = [ 656 | { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 }, 657 | ] 658 | 659 | [[package]] 660 | name = "pygments" 661 | version = "2.18.0" 662 | source = { registry = "https://pypi.org/simple" } 663 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } 664 | wheels = [ 665 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, 666 | ] 667 | 668 | [[package]] 669 | name = "pylint" 670 | version = "3.3.2" 671 | source = { registry = "https://pypi.org/simple" } 672 | dependencies = [ 673 | { name = "astroid" }, 674 | { name = "colorama", marker = "sys_platform == 'win32'" }, 675 | { name = "dill" }, 676 | { name = "isort" }, 677 | { name = "mccabe" }, 678 | { name = "platformdirs" }, 679 | { name = "tomlkit" }, 680 | ] 681 | sdist = { url = "https://files.pythonhosted.org/packages/81/d8/4471b2cb4ad18b4af717918c468209bd2bd5a02c52f60be5ee8a71b5af2c/pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01", size = 1516485 } 682 | wheels = [ 683 | { url = "https://files.pythonhosted.org/packages/61/55/5eaf6c415f6ddb09b9b039278823a8e27fb81ea7a34ec80c6d9223b17f2e/pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a", size = 521873 }, 684 | ] 685 | 686 | [[package]] 687 | name = "pytest" 688 | version = "8.3.4" 689 | source = { registry = "https://pypi.org/simple" } 690 | dependencies = [ 691 | { name = "colorama", marker = "sys_platform == 'win32'" }, 692 | { name = "iniconfig" }, 693 | { name = "packaging" }, 694 | { name = "pluggy" }, 695 | ] 696 | sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } 697 | wheels = [ 698 | { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, 699 | ] 700 | 701 | [[package]] 702 | name = "pytest-asyncio" 703 | version = "0.24.0" 704 | source = { registry = "https://pypi.org/simple" } 705 | dependencies = [ 706 | { name = "pytest" }, 707 | ] 708 | sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855 } 709 | wheels = [ 710 | { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 }, 711 | ] 712 | 713 | [[package]] 714 | name = "pytest-cov" 715 | version = "6.0.0" 716 | source = { registry = "https://pypi.org/simple" } 717 | dependencies = [ 718 | { name = "coverage" }, 719 | { name = "pytest" }, 720 | ] 721 | sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } 722 | wheels = [ 723 | { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, 724 | ] 725 | 726 | [[package]] 727 | name = "python-lsp-jsonrpc" 728 | version = "1.1.2" 729 | source = { registry = "https://pypi.org/simple" } 730 | dependencies = [ 731 | { name = "ujson" }, 732 | ] 733 | sdist = { url = "https://files.pythonhosted.org/packages/48/b6/fd92e2ea4635d88966bb42c20198df1a981340f07843b5e3c6694ba3557b/python-lsp-jsonrpc-1.1.2.tar.gz", hash = "sha256:4688e453eef55cd952bff762c705cedefa12055c0aec17a06f595bcc002cc912", size = 15298 } 734 | wheels = [ 735 | { url = "https://files.pythonhosted.org/packages/cb/d9/656659d5b5d5f402b2b174cd0ba9bc827e07ce3c0bf88da65424baf64af8/python_lsp_jsonrpc-1.1.2-py3-none-any.whl", hash = "sha256:7339c2e9630ae98903fdaea1ace8c47fba0484983794d6aafd0bd8989be2b03c", size = 8805 }, 736 | ] 737 | 738 | [[package]] 739 | name = "python-lsp-ruff" 740 | version = "2.2.2" 741 | source = { registry = "https://pypi.org/simple" } 742 | dependencies = [ 743 | { name = "cattrs" }, 744 | { name = "lsprotocol" }, 745 | { name = "python-lsp-server" }, 746 | { name = "ruff" }, 747 | ] 748 | sdist = { url = "https://files.pythonhosted.org/packages/ea/ec/475febe2f9e799f44afa476a2c0e063368d4289a65b80457ed737f6d05c0/python_lsp_ruff-2.2.2.tar.gz", hash = "sha256:3f80bdb0b4a8ee24624596a1cff60b28cc37771773730f9bf7d946ddff9f0cac", size = 15951 } 749 | wheels = [ 750 | { url = "https://files.pythonhosted.org/packages/c4/b1/d09777c49a5273d9a79fca24341284d588203dc8587120300e3f86d43858/python_lsp_ruff-2.2.2-py3-none-any.whl", hash = "sha256:7034d16c5cfdf07e932195649ebef569a7ddfcc5853fb2fee05fa7fc739afe3a", size = 11256 }, 751 | ] 752 | 753 | [[package]] 754 | name = "python-lsp-server" 755 | version = "1.12.0" 756 | source = { registry = "https://pypi.org/simple" } 757 | dependencies = [ 758 | { name = "docstring-to-markdown" }, 759 | { name = "jedi" }, 760 | { name = "pluggy" }, 761 | { name = "python-lsp-jsonrpc" }, 762 | { name = "ujson" }, 763 | ] 764 | sdist = { url = "https://files.pythonhosted.org/packages/2b/15/b7e1577b9ca358e008b06910bf23cfa0a8be130ee9f319a262a3c610ee8d/python_lsp_server-1.12.0.tar.gz", hash = "sha256:b6a336f128da03bd9bac1e61c3acca6e84242b8b31055a1ccf49d83df9dc053b", size = 114328 } 765 | wheels = [ 766 | { url = "https://files.pythonhosted.org/packages/1d/49/37c9659f76dbf1018d88892c14184db36ce9df09ea7d760162584aee8a58/python_lsp_server-1.12.0-py3-none-any.whl", hash = "sha256:2e912c661881d85f67f2076e4e66268b695b62bf127e07e81f58b187d4bb6eda", size = 74782 }, 767 | ] 768 | 769 | [package.optional-dependencies] 770 | all = [ 771 | { name = "autopep8" }, 772 | { name = "flake8" }, 773 | { name = "mccabe" }, 774 | { name = "pycodestyle" }, 775 | { name = "pydocstyle" }, 776 | { name = "pyflakes" }, 777 | { name = "pylint" }, 778 | { name = "rope" }, 779 | { name = "whatthepatch" }, 780 | { name = "yapf" }, 781 | ] 782 | 783 | [[package]] 784 | name = "pytoolconfig" 785 | version = "1.3.1" 786 | source = { registry = "https://pypi.org/simple" } 787 | dependencies = [ 788 | { name = "packaging" }, 789 | ] 790 | sdist = { url = "https://files.pythonhosted.org/packages/18/dc/abf70d2c2bcac20e8c71a7cdf6d44e4ddba4edf65acb179248d554d743db/pytoolconfig-1.3.1.tar.gz", hash = "sha256:51e6bd1a6f108238ae6aab6a65e5eed5e75d456be1c2bf29b04e5c1e7d7adbae", size = 16655 } 791 | wheels = [ 792 | { url = "https://files.pythonhosted.org/packages/92/44/da239917f5711ca7105f7d7f9e2765716dd883b241529beafc0f28504725/pytoolconfig-1.3.1-py3-none-any.whl", hash = "sha256:5d8cea8ae1996938ec3eaf44567bbc5ef1bc900742190c439a44a704d6e1b62b", size = 17022 }, 793 | ] 794 | 795 | [package.optional-dependencies] 796 | global = [ 797 | { name = "platformdirs" }, 798 | ] 799 | 800 | [[package]] 801 | name = "rich" 802 | version = "13.9.4" 803 | source = { registry = "https://pypi.org/simple" } 804 | dependencies = [ 805 | { name = "markdown-it-py" }, 806 | { name = "pygments" }, 807 | ] 808 | sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } 809 | wheels = [ 810 | { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, 811 | ] 812 | 813 | [[package]] 814 | name = "rope" 815 | version = "1.13.0" 816 | source = { registry = "https://pypi.org/simple" } 817 | dependencies = [ 818 | { name = "pytoolconfig", extra = ["global"] }, 819 | ] 820 | sdist = { url = "https://files.pythonhosted.org/packages/1c/c1/875e0270ac39b764fcb16c2dfece14a42747dbd0f181ac3864bff3126af1/rope-1.13.0.tar.gz", hash = "sha256:51437d2decc8806cd5e9dd1fd9c1306a6d9075ecaf78d191af85fc1dfface880", size = 294457 } 821 | wheels = [ 822 | { url = "https://files.pythonhosted.org/packages/a0/d0/e213e5adfa162e437dff3669131dc476043fc3a22fe99ef891516100610d/rope-1.13.0-py3-none-any.whl", hash = "sha256:b435a0c0971244fdcd8741676a9fae697ae614c20cc36003678a7782f25c0d6c", size = 206474 }, 823 | ] 824 | 825 | [[package]] 826 | name = "ruff" 827 | version = "0.8.3" 828 | source = { registry = "https://pypi.org/simple" } 829 | sdist = { url = "https://files.pythonhosted.org/packages/bf/5e/683c7ef7a696923223e7d95ca06755d6e2acbc5fd8382b2912a28008137c/ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3", size = 3378522 } 830 | wheels = [ 831 | { url = "https://files.pythonhosted.org/packages/f8/c4/bfdbb8b9c419ff3b52479af8581026eeaac3764946fdb463dec043441b7d/ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6", size = 10535860 }, 832 | { url = "https://files.pythonhosted.org/packages/ef/c5/0aabdc9314b4b6f051168ac45227e2aa8e1c6d82718a547455e40c9c9faa/ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939", size = 10346327 }, 833 | { url = "https://files.pythonhosted.org/packages/1a/78/4843a59e7e7b398d6019cf91ab06502fd95397b99b2b858798fbab9151f5/ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d", size = 9942585 }, 834 | { url = "https://files.pythonhosted.org/packages/91/5a/642ed8f1ba23ffc2dd347697e01eef3c42fad6ac76603be4a8c3a9d6311e/ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13", size = 10797597 }, 835 | { url = "https://files.pythonhosted.org/packages/30/25/2e654bc7226da09a49730a1a2ea6e89f843b362db80b4b2a7a4f948ac986/ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18", size = 10307244 }, 836 | { url = "https://files.pythonhosted.org/packages/c0/2d/a224d56bcd4383583db53c2b8f410ebf1200866984aa6eb9b5a70f04e71f/ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502", size = 11362439 }, 837 | { url = "https://files.pythonhosted.org/packages/82/01/03e2857f9c371b8767d3e909f06a33bbdac880df17f17f93d6f6951c3381/ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d", size = 12078538 }, 838 | { url = "https://files.pythonhosted.org/packages/af/ae/ff7f97b355da16d748ceec50e1604a8215d3659b36b38025a922e0612e9b/ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82", size = 11616172 }, 839 | { url = "https://files.pythonhosted.org/packages/6a/d0/6156d4d1e53ebd17747049afe801c5d7e3014d9b2f398b9236fe36ba4320/ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452", size = 12919886 }, 840 | { url = "https://files.pythonhosted.org/packages/4e/84/affcb30bacb94f6036a128ad5de0e29f543d3f67ee42b490b17d68e44b8a/ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd", size = 11212599 }, 841 | { url = "https://files.pythonhosted.org/packages/60/b9/5694716bdefd8f73df7c0104334156c38fb0f77673d2966a5a1345bab94d/ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20", size = 10784637 }, 842 | { url = "https://files.pythonhosted.org/packages/24/7e/0e8f835103ac7da81c3663eedf79dec8359e9ae9a3b0d704bae50be59176/ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc", size = 10390591 }, 843 | { url = "https://files.pythonhosted.org/packages/27/da/180ec771fc01c004045962ce017ca419a0281f4bfaf867ed0020f555b56e/ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060", size = 10894298 }, 844 | { url = "https://files.pythonhosted.org/packages/6d/f8/29f241742ed3954eb2222314b02db29f531a15cab3238d1295e8657c5f18/ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea", size = 11275965 }, 845 | { url = "https://files.pythonhosted.org/packages/79/e9/5b81dc9afc8a80884405b230b9429efeef76d04caead904bd213f453b973/ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964", size = 8807651 }, 846 | { url = "https://files.pythonhosted.org/packages/ea/67/7291461066007617b59a707887b90e319b6a043c79b4d19979f86b7a20e7/ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9", size = 9625289 }, 847 | { url = "https://files.pythonhosted.org/packages/03/8f/e4fa95288b81233356d9a9dcaed057e5b0adc6399aa8fd0f6d784041c9c3/ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936", size = 9078754 }, 848 | ] 849 | 850 | [[package]] 851 | name = "snowballstemmer" 852 | version = "2.2.0" 853 | source = { registry = "https://pypi.org/simple" } 854 | sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } 855 | wheels = [ 856 | { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, 857 | ] 858 | 859 | [[package]] 860 | name = "textual" 861 | version = "1.0.0" 862 | source = { registry = "https://pypi.org/simple" } 863 | dependencies = [ 864 | { name = "markdown-it-py", extra = ["linkify", "plugins"] }, 865 | { name = "platformdirs" }, 866 | { name = "rich" }, 867 | { name = "typing-extensions" }, 868 | ] 869 | sdist = { url = "https://files.pythonhosted.org/packages/1f/b6/59b1de04bb4dca0f21ed7ba0b19309ed7f3f5de4396edf20cc2855e53085/textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399", size = 1532733 } 870 | wheels = [ 871 | { url = "https://files.pythonhosted.org/packages/ac/bb/5fb6656c625019cd653d5215237d7cd6e0b12e7eae4195c3d1c91b2136fc/textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f", size = 660456 }, 872 | ] 873 | 874 | [[package]] 875 | name = "textual-dev" 876 | version = "1.7.0" 877 | source = { registry = "https://pypi.org/simple" } 878 | dependencies = [ 879 | { name = "aiohttp" }, 880 | { name = "click" }, 881 | { name = "msgpack" }, 882 | { name = "textual" }, 883 | { name = "textual-serve" }, 884 | { name = "typing-extensions" }, 885 | ] 886 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d3/ed0b20f6de0af1b7062c402d59d256029c0daa055ad9e04c27471b450cdd/textual_dev-1.7.0.tar.gz", hash = "sha256:bf1a50eaaff4cd6a863535dd53f06dbbd62617c371604f66f56de3908220ccd5", size = 25935 } 887 | wheels = [ 888 | { url = "https://files.pythonhosted.org/packages/50/4b/3c1eb9cbc39f2f28d27e10ef2fe42bfe0cf3c2f8445a454c124948d6169b/textual_dev-1.7.0-py3-none-any.whl", hash = "sha256:a93a846aeb6a06edb7808504d9c301565f7f4bf2e7046d56583ed755af356c8d", size = 27221 }, 889 | ] 890 | 891 | [[package]] 892 | name = "textual-serve" 893 | version = "1.1.1" 894 | source = { registry = "https://pypi.org/simple" } 895 | dependencies = [ 896 | { name = "aiohttp" }, 897 | { name = "aiohttp-jinja2" }, 898 | { name = "jinja2" }, 899 | { name = "rich" }, 900 | { name = "textual" }, 901 | ] 902 | sdist = { url = "https://files.pythonhosted.org/packages/18/6c/57248070f525ea8a9a02d9f58dc2747c609b615b0bda1306aaeb80a233bd/textual_serve-1.1.1.tar.gz", hash = "sha256:71c662472c462e5e368defc660ee6e8eae3bfda88ca40c050c55474686eb0c54", size = 445957 } 903 | wheels = [ 904 | { url = "https://files.pythonhosted.org/packages/07/a9/01d35770fde8d889e1fe28b726188cf28801e57afd369c614cd2bc100ee4/textual_serve-1.1.1-py3-none-any.whl", hash = "sha256:568782f1c0e60e3f7039d9121e1cb5c2f4ca1aaf6d6bd7aeb833d5763a534cb2", size = 445034 }, 905 | ] 906 | 907 | [[package]] 908 | name = "tomlkit" 909 | version = "0.13.2" 910 | source = { registry = "https://pypi.org/simple" } 911 | sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } 912 | wheels = [ 913 | { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, 914 | ] 915 | 916 | [[package]] 917 | name = "typing-extensions" 918 | version = "4.12.2" 919 | source = { registry = "https://pypi.org/simple" } 920 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 921 | wheels = [ 922 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 923 | ] 924 | 925 | [[package]] 926 | name = "uc-micro-py" 927 | version = "1.0.3" 928 | source = { registry = "https://pypi.org/simple" } 929 | sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043 } 930 | wheels = [ 931 | { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229 }, 932 | ] 933 | 934 | [[package]] 935 | name = "ujson" 936 | version = "5.10.0" 937 | source = { registry = "https://pypi.org/simple" } 938 | sdist = { url = "https://files.pythonhosted.org/packages/f0/00/3110fd566786bfa542adb7932d62035e0c0ef662a8ff6544b6643b3d6fd7/ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1", size = 7154885 } 939 | wheels = [ 940 | { url = "https://files.pythonhosted.org/packages/e8/a6/fd3f8bbd80842267e2d06c3583279555e8354c5986c952385199d57a5b6c/ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5", size = 55642 }, 941 | { url = "https://files.pythonhosted.org/packages/a8/47/dd03fd2b5ae727e16d5d18919b383959c6d269c7b948a380fdd879518640/ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e", size = 51807 }, 942 | { url = "https://files.pythonhosted.org/packages/25/23/079a4cc6fd7e2655a473ed9e776ddbb7144e27f04e8fc484a0fb45fe6f71/ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043", size = 51972 }, 943 | { url = "https://files.pythonhosted.org/packages/04/81/668707e5f2177791869b624be4c06fb2473bf97ee33296b18d1cf3092af7/ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1", size = 53686 }, 944 | { url = "https://files.pythonhosted.org/packages/bd/50/056d518a386d80aaf4505ccf3cee1c40d312a46901ed494d5711dd939bc3/ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3", size = 58591 }, 945 | { url = "https://files.pythonhosted.org/packages/fc/d6/aeaf3e2d6fb1f4cfb6bf25f454d60490ed8146ddc0600fae44bfe7eb5a72/ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21", size = 997853 }, 946 | { url = "https://files.pythonhosted.org/packages/f8/d5/1f2a5d2699f447f7d990334ca96e90065ea7f99b142ce96e85f26d7e78e2/ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2", size = 1140689 }, 947 | { url = "https://files.pythonhosted.org/packages/f2/2c/6990f4ccb41ed93744aaaa3786394bca0875503f97690622f3cafc0adfde/ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e", size = 1043576 }, 948 | { url = "https://files.pythonhosted.org/packages/14/f5/a2368463dbb09fbdbf6a696062d0c0f62e4ae6fa65f38f829611da2e8fdd/ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e", size = 38764 }, 949 | { url = "https://files.pythonhosted.org/packages/59/2d/691f741ffd72b6c84438a93749ac57bf1a3f217ac4b0ea4fd0e96119e118/ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc", size = 42211 }, 950 | { url = "https://files.pythonhosted.org/packages/0d/69/b3e3f924bb0e8820bb46671979770c5be6a7d51c77a66324cdb09f1acddb/ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287", size = 55646 }, 951 | { url = "https://files.pythonhosted.org/packages/32/8a/9b748eb543c6cabc54ebeaa1f28035b1bd09c0800235b08e85990734c41e/ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e", size = 51806 }, 952 | { url = "https://files.pythonhosted.org/packages/39/50/4b53ea234413b710a18b305f465b328e306ba9592e13a791a6a6b378869b/ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557", size = 51975 }, 953 | { url = "https://files.pythonhosted.org/packages/b4/9d/8061934f960cdb6dd55f0b3ceeff207fcc48c64f58b43403777ad5623d9e/ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988", size = 53693 }, 954 | { url = "https://files.pythonhosted.org/packages/f5/be/7bfa84b28519ddbb67efc8410765ca7da55e6b93aba84d97764cd5794dbc/ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816", size = 58594 }, 955 | { url = "https://files.pythonhosted.org/packages/48/eb/85d465abafb2c69d9699cfa5520e6e96561db787d36c677370e066c7e2e7/ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20", size = 997853 }, 956 | { url = "https://files.pythonhosted.org/packages/9f/76/2a63409fc05d34dd7d929357b7a45e3a2c96f22b4225cd74becd2ba6c4cb/ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0", size = 1140694 }, 957 | { url = "https://files.pythonhosted.org/packages/45/ed/582c4daba0f3e1688d923b5cb914ada1f9defa702df38a1916c899f7c4d1/ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f", size = 1043580 }, 958 | { url = "https://files.pythonhosted.org/packages/d7/0c/9837fece153051e19c7bade9f88f9b409e026b9525927824cdf16293b43b/ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165", size = 38766 }, 959 | { url = "https://files.pythonhosted.org/packages/d7/72/6cb6728e2738c05bbe9bd522d6fc79f86b9a28402f38663e85a28fddd4a0/ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539", size = 42212 }, 960 | ] 961 | 962 | [[package]] 963 | name = "whatthepatch" 964 | version = "1.0.7" 965 | source = { registry = "https://pypi.org/simple" } 966 | sdist = { url = "https://files.pythonhosted.org/packages/06/28/55bc3e107a56fdcf7d5022cb32b8c21d98a9cc2df5cd9f3b93e10419099e/whatthepatch-1.0.7.tar.gz", hash = "sha256:9eefb4ebea5200408e02d413d2b4bc28daea6b78bb4b4d53431af7245f7d7edf", size = 34612 } 967 | wheels = [ 968 | { url = "https://files.pythonhosted.org/packages/8e/93/af1d6ccb69ab6b5a00e03fa0cefa563f9862412667776ea15dd4eece3a90/whatthepatch-1.0.7-py3-none-any.whl", hash = "sha256:1b6f655fd31091c001c209529dfaabbabdbad438f5de14e3951266ea0fc6e7ed", size = 11964 }, 969 | ] 970 | 971 | [[package]] 972 | name = "yakari" 973 | version = "0.1.0" 974 | source = { editable = "." } 975 | dependencies = [ 976 | { name = "pydantic" }, 977 | { name = "rich" }, 978 | { name = "textual" }, 979 | { name = "tomlkit" }, 980 | ] 981 | 982 | [package.dev-dependencies] 983 | dev = [ 984 | { name = "pytest" }, 985 | { name = "pytest-asyncio" }, 986 | { name = "pytest-cov" }, 987 | { name = "python-lsp-ruff" }, 988 | { name = "python-lsp-server", extra = ["all"] }, 989 | { name = "ruff" }, 990 | { name = "textual-dev" }, 991 | ] 992 | 993 | [package.metadata] 994 | requires-dist = [ 995 | { name = "pydantic", specifier = ">=2.9.2" }, 996 | { name = "rich", specifier = ">=13.9.4" }, 997 | { name = "textual", specifier = ">=0.86.1" }, 998 | { name = "tomlkit", specifier = ">=0.13.2" }, 999 | ] 1000 | 1001 | [package.metadata.requires-dev] 1002 | dev = [ 1003 | { name = "pytest", specifier = ">=8.3.3" }, 1004 | { name = "pytest-asyncio", specifier = ">=0.24.0" }, 1005 | { name = "pytest-cov", specifier = ">=6.0.0" }, 1006 | { name = "python-lsp-ruff", specifier = ">=2.2.2" }, 1007 | { name = "python-lsp-server", extras = ["all"], specifier = ">=1.12.0" }, 1008 | { name = "ruff", specifier = ">=0.7.4" }, 1009 | { name = "textual-dev", specifier = ">=1.6.1" }, 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "yapf" 1014 | version = "0.43.0" 1015 | source = { registry = "https://pypi.org/simple" } 1016 | dependencies = [ 1017 | { name = "platformdirs" }, 1018 | ] 1019 | sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907 } 1020 | wheels = [ 1021 | { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158 }, 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "yarl" 1026 | version = "1.18.3" 1027 | source = { registry = "https://pypi.org/simple" } 1028 | dependencies = [ 1029 | { name = "idna" }, 1030 | { name = "multidict" }, 1031 | { name = "propcache" }, 1032 | ] 1033 | sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } 1034 | wheels = [ 1035 | { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, 1036 | { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, 1037 | { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, 1038 | { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, 1039 | { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, 1040 | { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, 1041 | { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, 1042 | { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, 1043 | { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, 1044 | { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, 1045 | { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, 1046 | { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, 1047 | { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, 1048 | { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, 1049 | { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, 1050 | { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, 1051 | { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, 1052 | { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, 1053 | { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, 1054 | { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, 1055 | { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, 1056 | { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, 1057 | { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, 1058 | { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, 1059 | { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, 1060 | { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, 1061 | { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, 1062 | { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, 1063 | { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, 1064 | { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, 1065 | { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, 1066 | { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, 1067 | { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, 1068 | ] 1069 | -------------------------------------------------------------------------------- /yakari/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlandeiro/yakari/0e602f6fbc04a470baa4a63c942563ff1bd2713c/yakari/.DS_Store -------------------------------------------------------------------------------- /yakari/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlandeiro/yakari/0e602f6fbc04a470baa4a63c942563ff1bd2713c/yakari/__init__.py -------------------------------------------------------------------------------- /yakari/app.css: -------------------------------------------------------------------------------- 1 | MenuScreen { 2 | padding-top: 1; 3 | } 4 | 5 | MenuScreen > Static { 6 | padding-left: 1; 7 | } 8 | 9 | MenuScreen > Label { 10 | margin: 0 1; 11 | } 12 | 13 | Footer { 14 | layout: horizontal; 15 | dock: bottom; 16 | background: $background-lighten-1; 17 | height: 1; 18 | width: 100%; 19 | padding: 0; 20 | } 21 | 22 | Footer { 23 | #cur-input { 24 | width: 10fr; 25 | align: left middle; 26 | } 27 | 28 | #help-section { 29 | width: 80fr; 30 | align: center middle; 31 | } 32 | 33 | #help-section Label { 34 | margin: 0 1; 35 | } 36 | 37 | Horizontal .title { 38 | color: $accent-darken-1; 39 | text-style: bold; 40 | } 41 | 42 | Horizontal .help { 43 | color: $primary-lighten-3; 44 | text-style: bold; 45 | } 46 | 47 | #edit-hint { 48 | align: right middle; 49 | width: 5fr; 50 | } 51 | } 52 | 53 | ModalScreen Input { 54 | border: none; 55 | height: auto; 56 | margin: 0; 57 | padding: 0; 58 | } 59 | 60 | ModalScreen > Vertical { 61 | align: center middle; 62 | } 63 | 64 | ResultsScreen > CommandRunner { 65 | width: 100%; 66 | height: 90vh; 67 | border-top: $primary wide; 68 | border-title-align: center; 69 | border-title-style: bold; 70 | margin: 0; 71 | dock: bottom; 72 | } 73 | 74 | ResultsScreen RichLog { 75 | padding: 1; 76 | background: $background; 77 | } 78 | 79 | ModalScreen.input-screen { 80 | layout: vertical; 81 | align: center middle; 82 | } 83 | 84 | ModalScreen.input-screen > .input-widget { 85 | height: auto; 86 | max-width: 70w; 87 | } 88 | 89 | 90 | SuggestionsWidget > OptionList { 91 | border: $primary wide; 92 | max-height: 15; 93 | } 94 | 95 | ArgumentInput { 96 | layout: vertical; 97 | align: center top; 98 | border: $primary wide; 99 | padding: 1; 100 | } 101 | 102 | ArgumentInput Input { 103 | border: none; 104 | padding: 0 1; 105 | } 106 | 107 | TagsCollection { 108 | layout: vertical; 109 | height: auto; 110 | } 111 | 112 | TagsCollection > ScrollableContainer { 113 | height: auto; 114 | max-height: 15; 115 | } 116 | 117 | Tag { 118 | background: $background-lighten-1; 119 | layout: horizontal; 120 | height: auto; 121 | width: auto; 122 | margin-top: 1; 123 | padding: 0; 124 | border: none; 125 | 126 | &:focus { 127 | background-tint: $foreground 20%; 128 | 129 | Button { 130 | background: $error; 131 | } 132 | } 133 | } 134 | 135 | Tag > Button { 136 | background: $primary; 137 | min-width: 3; 138 | width: auto; 139 | height: auto; 140 | margin: 0 0; 141 | padding: 0; 142 | border: none; 143 | } 144 | 145 | Tag > Label { 146 | margin: 0 1; 147 | height: auto; 148 | } 149 | -------------------------------------------------------------------------------- /yakari/app.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from textual.app import App 4 | 5 | from .types import Menu 6 | from .screens import MenuScreen, ResultsScreen 7 | 8 | 9 | class YakariApp(App): 10 | CSS_PATH = "app.css" 11 | ENABLE_COMMAND_PALETTE = False 12 | BINDINGS = [ 13 | ("ctrl+q", "quit", "quit"), 14 | ] 15 | 16 | def __init__( 17 | self, 18 | command_or_menu: str | Path | Menu, 19 | dry_run: bool = False, 20 | inplace: bool = False, 21 | ): 22 | super().__init__() 23 | self.command = None 24 | self.dry_run = dry_run 25 | self.inplace = inplace 26 | 27 | match command_or_menu: 28 | case Menu(): 29 | self.menu = command_or_menu 30 | case _: 31 | self.menu = Menu.from_toml(command_or_menu) 32 | self.menu_screen = MenuScreen(self.menu, is_entrypoint=True) 33 | self.results_screen = ResultsScreen() 34 | 35 | def on_mount(self) -> None: 36 | self.install_screen(self.results_screen, "results") 37 | self.install_screen(self.menu_screen, self.menu.name) 38 | self.push_screen(self.menu.name) 39 | -------------------------------------------------------------------------------- /yakari/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import subprocess 3 | 4 | from .app import YakariApp 5 | 6 | 7 | def main(): 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("command_name", help="Name of the command to execute") 10 | parser.add_argument( 11 | "-d", 12 | "--dry-run", 13 | action="store_true", 14 | help="If toggled, Yakari only prints the command rather than running it.", 15 | ) 16 | parser.add_argument( 17 | "-n", 18 | "--native", 19 | action="store_true", 20 | help="When toggled, run the command in the original shell instead of within the Yakari menu.", 21 | ) 22 | args = parser.parse_args() 23 | 24 | app = YakariApp(args.command_name, args.dry_run, not args.native) 25 | command = app.run() 26 | if command: 27 | subprocess.run(command) 28 | 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /yakari/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | # Menu constants 5 | DEFAULT_ARGUMENT_FIELDS = {"separator": "space", "multi_style": ","} 6 | 7 | DEFAULT_YAKARI_HOME = Path(os.environ["HOME"]) / ".config" / "yakari" 8 | YAKARI_HOME = Path(os.environ.get("YAKARI_HOME", DEFAULT_YAKARI_HOME)) 9 | 10 | MENUS_DIR = "menus" 11 | TEMPORARY_MENUS_DIR = "temporary_menus" 12 | HISTORY_FILENAME = "history" 13 | HISTORY_FILE = YAKARI_HOME / HISTORY_FILENAME 14 | 15 | REMOTE_DEFAULT = ( 16 | "https://raw.githubusercontent.com/vlandeiro/yakari-menus/refs/heads/main" 17 | ) 18 | 19 | HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True) 20 | -------------------------------------------------------------------------------- /yakari/rich_render.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from typing import DefaultDict, List, Tuple 3 | 4 | from rich.padding import Padding 5 | from rich.style import Style 6 | from rich.table import Column, Table 7 | from rich.text import Text 8 | 9 | from .types import ( 10 | Argument, 11 | ChoiceArgument, 12 | FlagArgument, 13 | Menu, 14 | Shortcut, 15 | ValueArgument, 16 | ) 17 | 18 | TABLE_CONFIG = dict( 19 | show_header=False, box=None, title_justify="left", title_style=Style(italic=True) 20 | ) 21 | TABLE_PADDING = (1, 0, 1, 0) 22 | DIM_STYLE = Style(dim=True) 23 | ENABLED_STYLE = Style(color="green", italic=True) 24 | HIGHLIGHT_STYLE = Style(color="green", bold=True) 25 | ERROR_STYLE = Style(color="red") 26 | 27 | 28 | def should_dim(key: str, user_input: str) -> str: 29 | """ 30 | Determine if a key should be dimmed based on user input. 31 | 32 | Args: 33 | key: The key to check 34 | user_input: The current user input string 35 | 36 | Returns: 37 | bool: True if the key should be dimmed, False otherwise 38 | """ 39 | return not key.startswith(user_input) 40 | 41 | 42 | def render_value(value: str | None, obfuscate: bool = False) -> str: 43 | """ 44 | Convert a value to its string representation for display. 45 | 46 | Args: 47 | value: The value to render, can be None or a string 48 | 49 | Returns: 50 | str: The rendered string representation of the value: 51 | - Empty string for None 52 | - Quoted empty string for "" 53 | - Original value otherwise 54 | """ 55 | if value is None: 56 | return "" 57 | elif value == "": # special case for a valid empty string input 58 | return '""' 59 | elif obfuscate: 60 | return "*" * min(len(value), 40) 61 | elif len(value) > 40: 62 | return f"{value[:18]}...{value[-18:]}" 63 | else: 64 | return str(value) 65 | 66 | 67 | def render_argument(argument: Argument) -> Tuple[str | Text, str | Text]: 68 | """ 69 | Render an argument into a displayable format. 70 | 71 | Args: 72 | argument: The Argument object to render (FlagArgument, ChoiceArgument, or ValueArgument) 73 | 74 | Returns: 75 | Tuple[str | Text, str | Text]: A tuple containing: 76 | - The rendered argument name/value 77 | - The rendered description with optional formatting 78 | 79 | Raises: 80 | ValueError: If the argument type is not supported 81 | """ 82 | match argument: 83 | case FlagArgument(): 84 | return (argument.flag, argument.description) 85 | case ChoiceArgument(): 86 | choices_str = " | ".join(argument.choices) 87 | choices_str = f"[ {choices_str} ]" 88 | choices_str = Text(choices_str) 89 | if argument.selected: 90 | choices_str.highlight_words( 91 | [f" {value} " for value in argument.selected], HIGHLIGHT_STYLE 92 | ) 93 | arg_value = argument.selected 94 | if not argument.multi and argument.selected: 95 | arg_value = argument.selected[0] 96 | return ( 97 | Text.assemble(argument.name, "=", render_value(arg_value)), 98 | Text.assemble(argument.description, " ", choices_str), 99 | ) 100 | case ValueArgument(): 101 | return ( 102 | f"{argument.name}={render_value(argument.value, obfuscate=argument.password)}", 103 | argument.description, 104 | ) 105 | case _: 106 | raise ValueError(f"{argument} of type {type(argument)} is not supported.") 107 | 108 | 109 | def render_key(key: str, user_input: str) -> Text: 110 | """ 111 | Render a key with optional styling. 112 | 113 | Args: 114 | key (str): The key to render 115 | user_input: The current user input string 116 | 117 | Returns: 118 | Union[Text, str]: Rendered key text with applied style 119 | """ 120 | if user_input: 121 | if key.startswith(user_input): 122 | key = Text.assemble( 123 | (user_input, HIGHLIGHT_STYLE), key.split(user_input, 1)[-1] 124 | ) 125 | else: 126 | key = Text(key) 127 | return key 128 | 129 | 130 | def group_arguments( 131 | arguments: List[Argument], 132 | ) -> DefaultDict[str, List[Tuple[Shortcut, Argument]]]: 133 | default_group = "Arguments" 134 | groups = defaultdict(list) 135 | 136 | for key, argument in arguments.items(): 137 | groups[argument.group or default_group].append((key, argument)) 138 | 139 | return groups 140 | 141 | 142 | def render_arguments_group( 143 | group_name: str, arguments: List[Tuple[Shortcut, Argument]], user_input: str 144 | ) -> Padding: 145 | """ 146 | Render a group of arguments as a formatted table with styling based on argument state. 147 | 148 | Args: 149 | group_name (str): Title of the argument group to be displayed 150 | arguments (List[Tuple[Shortcut, Argument]]): List of tuples containing shortcuts and their 151 | corresponding arguments to be rendered 152 | user_input (str): Current user input string used for determining styling 153 | 154 | Returns: 155 | Padding: A Rich Padding object containing the formatted table with proper spacing 156 | 157 | Note: 158 | The table includes columns for key, name, and description with appropriate styling: 159 | - Enabled arguments use ENABLED_STYLE 160 | - Dimmed arguments (based on user input) use DIM_STYLE 161 | """ 162 | table = Table( 163 | "key", "name", Column("desc", max_width=80), title=group_name, **TABLE_CONFIG 164 | ) 165 | for key, argument in arguments: 166 | style = None 167 | if should_dim(key, user_input): 168 | style = DIM_STYLE 169 | elif argument.enabled: 170 | style = ENABLED_STYLE 171 | 172 | key = render_key(key, user_input) 173 | table.add_row(key, *render_argument(argument), style=style) 174 | return Padding(table, TABLE_PADDING) 175 | 176 | 177 | def render_menu(menu: Menu, user_input: str): 178 | """ 179 | Generate a complete menu rendering including title, subcommands, arguments, and commands. 180 | 181 | Args: 182 | menu (Menu): Menu object containing all elements to be rendered 183 | user_input (str): Current user input string used for styling and filtering 184 | sort_by_keys (bool): Whether to sort the entries by their keyboard shortcuts. Defaults to True. 185 | 186 | Yields: 187 | Union[Text, Padding]: A sequence of Rich components representing different parts of the menu: 188 | - Menu title as Text 189 | - Subcommands table as Padding (if menu.menus exists) 190 | - Argument groups as Padding (if menu.arguments exists) 191 | - Commands table as Padding (if menu.commands exists) 192 | 193 | Note: 194 | Each section (subcommands, arguments, commands) is rendered in a separate table 195 | with appropriate formatting and column configurations defined in TABLE_CONFIG 196 | """ 197 | yield Text(menu.name, style="bold") 198 | if menu.menus: 199 | table = Table("key", "prefix", title="Subcommands", **TABLE_CONFIG) 200 | menu_items = ( 201 | sorted(menu.menus.items(), key=lambda x: x[0].lower()) 202 | if menu.configuration.sort_menus 203 | else menu.menus.items() 204 | ) 205 | for key, prefix in menu_items: 206 | style = None 207 | if should_dim(key, user_input): 208 | style = DIM_STYLE 209 | key = render_key(key, user_input) 210 | table.add_row(key, prefix.name, style=style) 211 | yield Padding(table, TABLE_PADDING) 212 | 213 | if menu.arguments: 214 | groups = group_arguments(menu.arguments) 215 | for group_name, arguments in groups.items(): 216 | arguments = ( 217 | sorted(arguments, key=lambda x: x[0].lower()) 218 | if menu.configuration.sort_arguments 219 | else arguments 220 | ) 221 | yield render_arguments_group(group_name, arguments, user_input) 222 | 223 | if menu.commands: 224 | table = Table( 225 | "key", 226 | "name", 227 | Column("desc", max_width=80), 228 | title="Commands", 229 | **TABLE_CONFIG, 230 | ) 231 | commands_items = ( 232 | sorted(menu.commands.items(), key=lambda x: x[0].lower()) 233 | if menu.configuration.sort_commands 234 | else menu.commands.items() 235 | ) 236 | for key, command in commands_items: 237 | style = None 238 | if should_dim(key, user_input): 239 | style = DIM_STYLE 240 | key = render_key(key, user_input) 241 | table.add_row(key, command.name, command.description, style=style) 242 | yield Padding(table, TABLE_PADDING) 243 | -------------------------------------------------------------------------------- /yakari/screens/__init__.py: -------------------------------------------------------------------------------- 1 | from .menu import MenuScreen 2 | from .choice_argument import ChoiceArgumentInputScreen 3 | from .value_argument import ValueArgumentInputScreen 4 | from .results import ResultsScreen 5 | -------------------------------------------------------------------------------- /yakari/screens/choice_argument.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from textual.app import ComposeResult 4 | from textual.screen import ModalScreen 5 | from textual.widgets import ( 6 | SelectionList as BaseSelectionList, 7 | OptionList as BaseOptionList, 8 | ) 9 | from textual.message import Message 10 | 11 | from ..types import ChoiceArgument 12 | from ..widgets import Footer 13 | 14 | 15 | class SelectionList(BaseSelectionList): 16 | BINDINGS = [ 17 | ("enter", "submit_selection", "submit"), 18 | ] 19 | 20 | class SelectionSubmitted(Message): 21 | def __init__(self, selection): 22 | super().__init__() 23 | self.selection = selection 24 | 25 | def action_submit_selection(self): 26 | self.post_message(self.SelectionSubmitted(self.selected)) 27 | 28 | 29 | class OptionList(BaseOptionList): 30 | BINDINGS = [("enter", "select", "submit")] 31 | pass 32 | 33 | 34 | class ChoiceArgumentInputScreen(ModalScreen[int | List[str] | None]): 35 | """A modal screen for selecting from a list of choices for an argument. 36 | 37 | Args: 38 | argument (ValueArgument): The argument containing the choices 39 | """ 40 | 41 | BINDINGS = [ 42 | ("enter", "submit_input", "submit"), 43 | ("ctrl+q", "cancel", "cancel"), 44 | ] 45 | 46 | def __init__(self, argument: ChoiceArgument): 47 | self.argument = argument 48 | if self.argument.multi: 49 | selected = set() 50 | if self.argument.selected: 51 | selected = set(argument.selected) 52 | selections = [ 53 | (choice, choice, True) if choice in selected else (choice, choice) 54 | for choice in argument.choices 55 | ] 56 | self.widget = SelectionList(*selections, classes="input-widget") 57 | self.result_attr = "selected" 58 | else: 59 | self.widget = OptionList(*argument.choices, classes="input-widget") 60 | self.result_attr = "highlighted" 61 | self.widget.border_title = self.argument.name 62 | super().__init__(classes="input-screen") 63 | 64 | def compose(self) -> ComposeResult: 65 | yield self.widget 66 | yield Footer() 67 | 68 | def on_option_list_option_selected(self, message: OptionList.OptionSelected): 69 | self.dismiss(message.option.prompt) 70 | 71 | def on_selection_list_selection_submitted( 72 | self, message: SelectionList.SelectionSubmitted 73 | ): 74 | self.dismiss(message.selection) 75 | 76 | def action_cancel(self): 77 | self.dismiss(None) 78 | -------------------------------------------------------------------------------- /yakari/screens/menu.py: -------------------------------------------------------------------------------- 1 | from typing import List, Literal 2 | 3 | from rich.text import Text 4 | from textual import events, work 5 | from textual.app import ComposeResult 6 | from textual.binding import Binding 7 | from textual.reactive import reactive 8 | from textual.screen import Screen 9 | from textual.widgets import ( 10 | Static, 11 | ) 12 | 13 | from ..rich_render import render_menu 14 | from ..types import ( 15 | Argument, 16 | ChoiceArgument, 17 | Command, 18 | CommandTemplateResolver, 19 | FlagArgument, 20 | MatchResult, 21 | Menu, 22 | ValueArgument, 23 | ) 24 | from ..widgets import Footer, CommandRunner 25 | from .choice_argument import ChoiceArgumentInputScreen 26 | from .value_argument import ValueArgumentInputScreen 27 | 28 | 29 | class MenuScreen(Screen): 30 | """Main screen showing the menu interface and handling user input. 31 | 32 | Attributes: 33 | cur_input (reactive): Current user input string 34 | """ 35 | 36 | BINDINGS = [ 37 | Binding("backspace", "backspace_input", "erase / go back"), 38 | Binding("tab", "complete_input", "complete", show=False), 39 | Binding("ctrl+e", "change_mode", "toggle edit mode"), 40 | Binding("ctrl+r", "show_results", "show results"), 41 | ] 42 | 43 | cur_input = reactive("", recompose=True) 44 | edit_mode = reactive(False, recompose=True) 45 | 46 | def __init__(self, menu: Menu, is_entrypoint: bool = False): 47 | super().__init__() 48 | self.menu = menu 49 | self.is_entrypoint = is_entrypoint 50 | self.candidates = {**menu.arguments, **menu.menus, **menu.commands} 51 | 52 | def compose(self) -> ComposeResult: 53 | for renderable in render_menu(self.menu, self.cur_input): 54 | yield Static(renderable) 55 | yield Footer() 56 | 57 | @work 58 | async def action_show_results(self): 59 | if self.app.inplace: 60 | await self.app.push_screen_wait("results") 61 | self.refresh_bindings() 62 | 63 | def action_backspace_input(self): 64 | """Remove the last character from current input.""" 65 | if self.cur_input: 66 | self.cur_input = self.cur_input[:-1] 67 | elif not self.is_entrypoint: 68 | self.dismiss(None) 69 | 70 | def action_change_mode(self): 71 | self.edit_mode = not self.edit_mode 72 | 73 | @work 74 | async def action_complete_input(self): 75 | """Handle tab completion of current input.""" 76 | 77 | # If we have only one remaining matching argument and we hit tab, 78 | # complete the current input and process the argument 79 | match_results = self.string_matches_candidates(self.cur_input) 80 | 81 | if len(match_results.partial_matches) == 1: 82 | self.cur_input = match_results.partial_matches[0] 83 | await self.process_match(self.candidates[self.cur_input]) 84 | 85 | @work 86 | async def on_key(self, event: events.Key) -> None: 87 | """Handle key press events. 88 | 89 | Args: 90 | event (events.Key): The key press event 91 | 92 | Handles printable characters by: 93 | - Processing exact matches immediately 94 | - Adding character to input if there are partial matches 95 | - Resetting input if no matches 96 | """ 97 | 98 | if event.is_printable: 99 | new_input = self.cur_input + event.character 100 | 101 | match_results = self.string_matches_candidates(new_input) 102 | 103 | # If we have an exact match, then process it 104 | if match_results.exact_match is not None: 105 | self.cur_input = new_input 106 | await self.process_match(self.candidates[new_input]) 107 | event.stop() 108 | 109 | # If we have partial matches, then we update the current 110 | # input with the new character 111 | elif match_results.partial_matches: 112 | self.cur_input = new_input 113 | 114 | # otherwise, we reset the current input 115 | else: 116 | self.cur_input = "" 117 | 118 | async def process_match(self, match_value: Argument | Command | Menu): 119 | """Process a matched menu item based on its type. 120 | 121 | Args: 122 | match_value: The matched argument, command or submenu 123 | """ 124 | match match_value: 125 | case Argument(): 126 | await self.process_argument( 127 | match_value, action="edit" if self.edit_mode else "toggle" 128 | ) 129 | case Command(): 130 | command = await self.process_command(match_value) 131 | if command is None: 132 | return 133 | 134 | case Menu(): 135 | await self.process_menu(match_value) 136 | 137 | async def process_argument( 138 | self, argument: Argument, action: Literal["edit"] | Literal["toggle"] = "toggle" 139 | ): 140 | """Handle processing of different argument types. 141 | 142 | Args: 143 | argument (Argument): The argument to process 144 | action (Literal["edit"] | Literal["toggle"]): Whether the targeted argument should be edited or 145 | toggled when active. This parameter has no effect for `FlagArgument` instances. 146 | 147 | Handles: 148 | - Toggling flag arguments 149 | - Getting choice selection from modal 150 | - Getting value input from modal 151 | """ 152 | match argument: 153 | case FlagArgument(): 154 | argument.on = not argument.on 155 | self.cur_input = "" 156 | case ChoiceArgument(): 157 | 158 | def set_argument_value_and_reset_input(value: str | List[str] | None): 159 | if value is None: 160 | argument.selected = None 161 | elif isinstance(value, str): 162 | argument.selected = [value] 163 | else: 164 | argument.selected = value 165 | self.cur_input = "" 166 | 167 | if argument.selected and action == "toggle": 168 | set_argument_value_and_reset_input(None) 169 | else: 170 | new_value = await self.app.push_screen_wait( 171 | ChoiceArgumentInputScreen(argument), 172 | ) 173 | set_argument_value_and_reset_input(new_value) 174 | case ValueArgument(): 175 | 176 | def set_argument_value_and_reset_input(value: str): 177 | argument.value = value 178 | self.cur_input = "" 179 | 180 | if argument.value is not None and action == "toggle": 181 | set_argument_value_and_reset_input(None) 182 | else: 183 | new_value = await self.app.push_screen_wait( 184 | ValueArgumentInputScreen(argument), 185 | ) 186 | set_argument_value_and_reset_input(new_value) 187 | 188 | async def process_command(self, command: Command): 189 | """Process a command by resolving its template and arguments. 190 | 191 | Args: 192 | command (Command): The command to process 193 | 194 | Resolves the command template with argument values and exits app 195 | with the final command. 196 | """ 197 | 198 | async def process_argument_fn(argument: Argument): 199 | return await self.process_argument(argument, action="edit") 200 | 201 | template_resolver = CommandTemplateResolver( 202 | process_argument_fn=process_argument_fn 203 | ) 204 | self.app.command = await template_resolver.resolve(self.menu, command.template) 205 | self.cur_input = "" 206 | 207 | if self.app.command is None: 208 | return 209 | 210 | results_widget: CommandRunner = self.app.results_screen.cmd_runner 211 | command_str = " ".join(self.app.command) 212 | 213 | inplace = command.inplace if command.inplace is not None else self.app.inplace 214 | 215 | if self.app.dry_run: 216 | if inplace: 217 | self.app.push_screen("results") 218 | results_widget.write(Text(f"$> {command_str}")) 219 | if not inplace: 220 | self.app.exit(result=None, return_code=0, message=command_str) 221 | 222 | else: 223 | if inplace: 224 | self.app.push_screen("results") 225 | results_widget.start_subprocess(self.app.command) 226 | else: 227 | self.app.exit( 228 | result=self.app.command, return_code=0, message=command_str 229 | ) 230 | 231 | async def process_menu(self, menu: Menu): 232 | """Process a submenu by pushing a new menu screen. 233 | 234 | Args: 235 | menu (Menu): The submenu to display 236 | """ 237 | menu._ancestors_arguments = self.menu.arguments 238 | await self.app.push_screen_wait(MenuScreen(menu)) 239 | self.cur_input = "" 240 | 241 | def string_matches_candidates(self, s: str) -> MatchResult: 242 | """Find matches for input string against available options. 243 | 244 | Args: 245 | s (str): Input string to match 246 | 247 | Returns: 248 | MatchResult: Contains exact and partial matches found 249 | """ 250 | candidates_set = set(self.candidates) 251 | exact_match = None 252 | partial_matches = [] 253 | if s in candidates_set: 254 | exact_match = s 255 | else: 256 | partial_matches = [key for key in candidates_set if key.startswith(s)] 257 | return MatchResult(exact_match=exact_match, partial_matches=partial_matches) 258 | -------------------------------------------------------------------------------- /yakari/screens/results.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.screen import ModalScreen 3 | 4 | from ..widgets import Footer, CommandRunner 5 | 6 | 7 | class ResultsScreen(ModalScreen): 8 | BINDINGS = [ 9 | ("ctrl+l", "clear_screen", "clear"), 10 | ("ctrl+r", "pop_screen", "hide results"), 11 | ] 12 | 13 | def __init__(self): 14 | super().__init__() 15 | self.cmd_runner = CommandRunner() 16 | self.cmd_runner.border_title = "Results" 17 | 18 | def compose(self) -> ComposeResult: 19 | yield self.cmd_runner 20 | yield Footer() 21 | 22 | def action_pop_screen(self): 23 | self.app.pop_screen() 24 | 25 | def action_clear_screen(self): 26 | self.cmd_runner.log_widget.clear() 27 | -------------------------------------------------------------------------------- /yakari/screens/value_argument.py: -------------------------------------------------------------------------------- 1 | import shelve 2 | 3 | from textual.app import ComposeResult 4 | from textual.screen import ModalScreen 5 | from textual.widgets import OptionList 6 | 7 | from .. import constants as C 8 | from ..types import ValueArgument 9 | from ..widgets import ArgumentInput, Footer, SuggestionsWidget 10 | 11 | 12 | class ValueArgumentInputScreen(ModalScreen[str | None]): 13 | """A modal screen for entering a value for an argument. 14 | 15 | Args: 16 | argument (ValueArgument): The argument to get input for 17 | """ 18 | 19 | BINDINGS = [ 20 | ("ctrl+q", "cancel", "cancel"), 21 | ] 22 | 23 | def __init__(self, argument: ValueArgument): 24 | super().__init__(classes="input-screen") 25 | self.argument = argument 26 | self._init_suggestions() 27 | self.input_widget = ArgumentInput( 28 | argument, self.suggested_values, classes="input-widget" 29 | ) 30 | self.input_widget.border_title = argument.name 31 | 32 | def _init_suggestions(self): 33 | self.suggested_values = [] 34 | self.suggestions_widget = None 35 | 36 | # Load suggestions from history 37 | if not self.argument.password: 38 | with shelve.open(C.HISTORY_FILE, writeback=True) as shelf: 39 | arg_history = shelf.get(self.argument.name, dict()) 40 | self.suggested_values.extend(list(arg_history)[::-1]) 41 | 42 | # Load suggestions from hard-coded list, executed command, or other methods 43 | if self.argument.suggestions: 44 | if self.suggested_values: 45 | self.suggested_values.append(None) 46 | self.suggested_values.extend(self.argument.suggestions.values) 47 | 48 | if self.suggested_values: 49 | self.suggestions_widget = SuggestionsWidget( 50 | self.suggested_values, classes="input-widget" 51 | ) 52 | 53 | def on_mount(self): 54 | self.input_widget.focus() 55 | 56 | def action_cancel(self): 57 | self.dismiss(None) 58 | 59 | def on_argument_input_submitted(self, message: ArgumentInput.Submitted): 60 | self.dismiss(message.value) 61 | 62 | def on_suggestions_widget_suggestion_selected( 63 | self, message: SuggestionsWidget.SuggestionSelected 64 | ): 65 | self.input_widget.set_value(message.value) 66 | self.input_widget.focus() 67 | message.stop() 68 | 69 | def compose(self) -> ComposeResult: 70 | if self.suggestions_widget is not None: 71 | yield self.suggestions_widget 72 | yield self.input_widget 73 | yield Footer() 74 | -------------------------------------------------------------------------------- /yakari/types.py: -------------------------------------------------------------------------------- 1 | """ 2 | A module for defining menu structure and commands using Pydantic models. 3 | This module provides classes to represent actions, commands, command groups, 4 | and complete menu structures in a type-safe way using Pydantic data validation. 5 | """ 6 | 7 | import subprocess 8 | import urllib 9 | import urllib.request 10 | from abc import ABC, abstractmethod 11 | from pathlib import Path 12 | from typing import Awaitable, Callable, Dict, List, Literal, Self 13 | 14 | import tomlkit 15 | from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator 16 | 17 | from . import constants as C 18 | 19 | 20 | Shortcut = str 21 | 22 | 23 | class YakariType(BaseModel): 24 | model_config = ConfigDict(extra="forbid") 25 | 26 | 27 | class History(YakariType): 28 | """A class managing command history with navigation capabilities.""" 29 | 30 | values: Dict[str, int] = Field(default_factory=dict) 31 | max_size: int = 20 32 | _cur_pos: int | None = PrivateAttr(default=None) 33 | 34 | def add(self, value: str): 35 | if not value: 36 | return 37 | 38 | if value in self.values: 39 | del self.values[value] 40 | self.values[value] = 1 41 | 42 | if len(self.values) > self.max_size: 43 | first_key = list(self.values.keys())[0] 44 | del self.values[first_key] 45 | 46 | 47 | class MatchResult(YakariType): 48 | """ 49 | Represents the result of a command/argument matching operation. 50 | 51 | Attributes: 52 | exact_match (str | None): The exact match found, if any 53 | partial_matches (List[str]): List of partial matches found 54 | """ 55 | 56 | exact_match: str | None = None 57 | partial_matches: List[str] = Field(default_factory=list) 58 | 59 | 60 | class Argument(YakariType): 61 | """ 62 | Base class for all command arguments, providing common functionality. 63 | 64 | Attributes: 65 | template (str | List[str] | None): A templated python string to render the argument. 66 | description (str): A short description explaining the argument's purpose. 67 | group (str | None): The name of the group this argument belongs to. 68 | """ 69 | 70 | template: str | List[str] | None = Field( 71 | default=None, 72 | description=( 73 | "A templated python string to represent the argument. " 74 | "It can use attributes available on `self`." 75 | ), 76 | ) 77 | description: str = Field( 78 | default="", description="A short description for the argument." 79 | ) 80 | group: str | None = Field( 81 | default=None, description="The name of the group this argument belongs to." 82 | ) 83 | 84 | def render_template(self) -> List[str] | str: 85 | match self.template: 86 | case list(): 87 | return [part.format(self=self) for part in self.template] 88 | case str(): 89 | return self.template.format(self=self) 90 | case _: 91 | return 92 | 93 | 94 | class FlagArgument(Argument): 95 | """ 96 | Represents a flag-style command argument that can be enabled or disabled. 97 | 98 | Attributes: 99 | flag (str): The flag string (e.g. '--verbose') 100 | on (bool): Whether the flag is enabled 101 | """ 102 | 103 | flag: str 104 | template: str = "{self.flag}" 105 | on: bool = Field( 106 | default=False, description="State of the flag argument, True when enabled." 107 | ) 108 | 109 | @property 110 | def enabled(self): 111 | return self.on 112 | 113 | 114 | class NamedArgument(Argument, ABC): 115 | name: str 116 | separator: Literal["space"] | Literal["equal"] = C.DEFAULT_ARGUMENT_FIELDS[ 117 | "separator" 118 | ] 119 | multi: bool = False 120 | multi_style: Literal["repeat"] | str = C.DEFAULT_ARGUMENT_FIELDS["multi_style"] 121 | 122 | @property 123 | def positional(self): 124 | return not self.name.startswith("-") 125 | 126 | @abstractmethod 127 | def get_value_list(self) -> List[str]: 128 | raise NotImplementedError() 129 | 130 | def render_template(self) -> List[str] | str: 131 | if self.template is not None: 132 | return super().render_template() 133 | 134 | values = self.get_value_list() 135 | if self.positional: 136 | if self.multi_style == "repeat": 137 | return values 138 | else: 139 | return self.multi_style.join(values) 140 | 141 | if not self.multi: 142 | value = values[0] 143 | match self.separator: 144 | case "space": 145 | return [self.name, value] 146 | case "equal": 147 | return f"{self.name}={value}" 148 | 149 | if self.multi_style == "repeat": 150 | result = [] 151 | for value in values: 152 | match self.separator: 153 | case "space": 154 | result.extend([self.name, value]) 155 | case "equal": 156 | result.append(f"{self.name}={value}") 157 | return result 158 | 159 | match self.separator: 160 | case "space": 161 | return [self.name, self.multi_style.join(values)] 162 | case "equal": 163 | return f"{self.name}={self.multi_style.join(values)}" 164 | 165 | 166 | class ChoiceArgument(NamedArgument): 167 | """ 168 | Represents a command argument that must be chosen from a predefined set of values. 169 | 170 | Attributes: 171 | name (str): Name of the argument 172 | choices (List[str]): Available values to choose from 173 | selected (str | None): Currently selected value 174 | """ 175 | 176 | choices: List[str] = Field( 177 | description="A list of available values for this argument." 178 | ) 179 | selected: str | List[str] | None = Field( 180 | default=None, 181 | description="The selection value for this argument.", 182 | ) 183 | 184 | @property 185 | def enabled(self): 186 | return self.selected is not None 187 | 188 | def get_value_list(self): 189 | if self.selected is None: 190 | return None 191 | elif isinstance(self.selected, str): 192 | return [self.selected] 193 | return self.selected 194 | 195 | @model_validator(mode="after") 196 | def sanitize_selected_value(self): 197 | if isinstance(self.selected, str): 198 | self.selected = [self.selected] 199 | return self 200 | 201 | 202 | class SuggestionsList(YakariType): 203 | values: List[str] 204 | 205 | 206 | class SuggestionsCommand(YakariType): 207 | command: str 208 | cache: bool = False 209 | _suggestions: List[str] = PrivateAttr(default_factory=list) 210 | 211 | @property 212 | def values(self): 213 | if not self.cache or not self._suggestions: 214 | result = subprocess.run(self.command, capture_output=True, shell=True) 215 | if result.stderr: 216 | raise RuntimeError( 217 | f"Command {self.command} failed with the following " 218 | f"message:\n{result.stderr.decode()}" 219 | ) 220 | self._suggestions = [ 221 | line.strip() 222 | for line in result.stdout.decode().split("\n") 223 | if line.strip() 224 | ] 225 | return self._suggestions 226 | 227 | 228 | SuggestionsImpl = SuggestionsList | SuggestionsCommand 229 | 230 | 231 | class ValueArgument(NamedArgument): 232 | """ 233 | Represents a command argument that accepts an arbitrary value. 234 | 235 | Attributes: 236 | name (str): Name of the argument. Should 237 | value (str | None): The argument's value 238 | password (bool): True if the argument represents a field that should be obfuscated. 239 | Defaults to False. 240 | """ 241 | 242 | value: str | List[str] | None = Field( 243 | default=None, description="The value for this argument." 244 | ) 245 | password: bool = False 246 | suggestions: SuggestionsImpl | None = None 247 | 248 | @property 249 | def enabled(self): 250 | return self.value is not None 251 | 252 | def get_value_list(self): 253 | if self.value is None: 254 | return None 255 | if isinstance(self.value, str): 256 | return [self.value] 257 | return self.value 258 | 259 | 260 | ArgumentImpl = FlagArgument | ValueArgument | ChoiceArgument 261 | 262 | 263 | class MenuArguments(YakariType): 264 | include: Literal["*"] | List[str] 265 | exclude: List[str] | None = None 266 | scope: Literal["this"] | Literal["all"] = "all" 267 | 268 | def resolve_arguments(self, menu: "Menu") -> Dict[Shortcut, Argument]: 269 | arguments = dict() 270 | if self.scope == "all": 271 | arguments.update(menu._ancestors_arguments) 272 | arguments.update(menu.arguments) 273 | 274 | if self.include != "*": 275 | arguments = { 276 | shortcut: arg 277 | for shortcut, arg in arguments.items() 278 | if shortcut in self.include 279 | } 280 | if self.exclude: 281 | arguments = { 282 | shortcut: arg 283 | for shortcut, arg in arguments.items() 284 | if shortcut not in self.exclude 285 | } 286 | 287 | return arguments 288 | 289 | 290 | CommandTemplate = List[str | MenuArguments | ArgumentImpl] 291 | 292 | 293 | class Command(YakariType): 294 | """ 295 | Represents a command with configurable arguments and template-based execution. 296 | 297 | Attributes: 298 | name (str): Unique identifier/name for the command 299 | description (str): Optional description explaining the command's purpose 300 | template (List[str | MenuArguments | ArgumentImpl]): List of components that make up the 301 | command, can include raw strings, deferred values, and various argument types 302 | """ 303 | 304 | name: str 305 | description: str = "" 306 | template: CommandTemplate 307 | inplace: bool | None = None 308 | 309 | 310 | class NamedArgumentsStyle(YakariType): 311 | separator: Literal["space"] | Literal["equal"] = C.DEFAULT_ARGUMENT_FIELDS[ 312 | "separator" 313 | ] 314 | multi_style: Literal["repeat"] | str = C.DEFAULT_ARGUMENT_FIELDS["multi_style"] 315 | 316 | 317 | class MenuConfiguration(YakariType): 318 | named_arguments_style: NamedArgumentsStyle = Field( 319 | default_factory=NamedArgumentsStyle 320 | ) 321 | sort_arguments: bool = True 322 | sort_commands: bool = True 323 | sort_menus: bool = True 324 | 325 | 326 | def set_default_arg_value(arg: Argument, configuration: MenuConfiguration): 327 | config_fields_set = configuration.named_arguments_style.model_fields_set 328 | arg_fields = arg.model_fields 329 | arg_fields_set = arg.model_fields_set 330 | for field_name in config_fields_set: 331 | if field_name in arg_fields and field_name not in arg_fields_set: 332 | default_value = getattr(configuration.named_arguments_style, field_name) 333 | setattr(arg, field_name, default_value) 334 | return arg 335 | 336 | 337 | class Menu(YakariType): 338 | """ 339 | Represents the complete menu structure containing groups of commands. 340 | 341 | Attributes: 342 | name (str): The name of the menu 343 | arguments (Dict[Shortcut, ArgumentImpl]): Available arguments mapped by shortcuts 344 | menus (Dict[Shortcut, Menu]): Sub-menus mapped by shortcuts 345 | commands (Dict[Shortcut, Command]): Available commands mapped by shortcuts 346 | """ 347 | 348 | name: str 349 | arguments: Dict[Shortcut, ArgumentImpl] = Field(default_factory=dict) 350 | menus: Dict[Shortcut, Self] = Field(default_factory=dict) 351 | commands: Dict[Shortcut, Command] = Field(default_factory=dict) 352 | configuration: MenuConfiguration = Field(default_factory=MenuConfiguration) 353 | 354 | _ancestors_arguments: Dict[Shortcut, Argument] = PrivateAttr(default_factory=dict) 355 | 356 | @classmethod 357 | def from_toml(cls, command_name: str | Path) -> Self: 358 | if isinstance(command_name, Path): # local path 359 | config_path = command_name 360 | 361 | elif command_name.startswith(("http://", "https://")): # url 362 | # resolve the URL to a local path 363 | url = command_name 364 | parsed_url = urllib.parse.urlparse(url) 365 | filename = urllib.parse.unquote(Path(parsed_url.path).name) 366 | config_path = C.YAKARI_HOME / C.TEMPORARY_MENUS_DIR / filename 367 | config_path.parent.mkdir(parents=True, exist_ok=True) 368 | urllib.request.urlretrieve(url, config_path) 369 | 370 | else: # command name 371 | base_path = C.YAKARI_HOME / C.MENUS_DIR 372 | config_path = (base_path / command_name).with_suffix(".toml") 373 | 374 | if not ( 375 | config_path.exists() and config_path.is_file() 376 | ): # no local configuration exists 377 | try: 378 | # try to retrieve an online configuration 379 | return cls.from_toml(f"{C.REMOTE_DEFAULT}/{command_name}.toml") 380 | except Exception: 381 | raise ValueError(f"No configuration found for '{command_name}'.") 382 | 383 | with config_path.open("r") as fd: 384 | model = tomlkit.load(fd).unwrap() 385 | return cls.model_validate(model) 386 | 387 | @model_validator(mode="after") 388 | def set_default_fields(self) -> Self: 389 | # propagate defaults to menu arguments 390 | for arg in self.arguments.values(): 391 | set_default_arg_value(arg, self.configuration) 392 | 393 | # propagate defaults to dynamic arguments in commands 394 | for command in self.commands.values(): 395 | for part in command.template: 396 | if isinstance(part, Argument): 397 | set_default_arg_value(part, self.configuration) 398 | 399 | # propagate the parent menu configuration to all child menus unless it 400 | # was explicitly set 401 | for menu in self.menus.values(): 402 | menu_fields_set = menu.model_fields_set 403 | if "configuration" not in menu_fields_set: 404 | menu.configuration = self.configuration 405 | menu.set_default_fields() 406 | 407 | return self 408 | 409 | 410 | class CommandTemplateResolver(YakariType): 411 | process_argument_fn: Callable[[Argument], Awaitable[None]] 412 | resolved_command: List[str] = Field(default_factory=list) 413 | 414 | async def resolve(self, menu: Menu, template: CommandTemplate) -> List[str] | None: 415 | resolved_command = [] 416 | 417 | def update_resolved_command(rendered_argument: str | List[str]) -> List[str]: 418 | match rendered_argument: 419 | case str(): 420 | resolved_command.append(rendered_argument) 421 | case list(): 422 | resolved_command.extend(rendered_argument) 423 | 424 | for part in template: 425 | match part: 426 | case str(): 427 | resolved_command.append(part) 428 | 429 | case Argument(): 430 | argument = part 431 | await self.process_argument_fn(argument) 432 | if argument.enabled: 433 | update_resolved_command(argument.render_template()) 434 | else: 435 | return None 436 | case MenuArguments(): 437 | arguments = part.resolve_arguments(menu) 438 | for key, argument in arguments.items(): 439 | if argument.enabled: 440 | update_resolved_command(argument.render_template()) 441 | 442 | return resolved_command 443 | -------------------------------------------------------------------------------- /yakari/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | from .argument_input import ArgumentInput 2 | from .footer import Footer 3 | from .suggestions import SuggestionsWidget 4 | from .tags import TagsCollection 5 | from .command_runner import CommandRunner 6 | -------------------------------------------------------------------------------- /yakari/widgets/argument_input.py: -------------------------------------------------------------------------------- 1 | import shelve 2 | 3 | from textual.app import ComposeResult 4 | from textual.message import Message 5 | from textual.suggester import SuggestFromList 6 | from textual.widget import Widget 7 | from textual.widgets import Input as BaseInput 8 | 9 | from .. import constants as C 10 | from ..types import Argument, History 11 | from .tags import TagsCollection 12 | 13 | 14 | class Input(BaseInput): 15 | BINDINGS = [("enter", "submit", "submit")] 16 | 17 | 18 | class ArgumentInput(Widget): 19 | BINDINGS = [("ctrl+q", "cancel", "cancel")] 20 | 21 | class Submitted(Message): 22 | def __init__(self, value: list[str] | str) -> None: 23 | self.value = value 24 | super().__init__() 25 | 26 | def __init__( 27 | self, 28 | argument: Argument, 29 | suggested_values: list[str] | None = None, 30 | *args, 31 | **kwargs, 32 | ): 33 | super().__init__(*args, **kwargs) 34 | self.argument = argument 35 | self.with_history = not argument.password 36 | self.tags = TagsCollection() 37 | 38 | suggester = None 39 | if suggested_values: 40 | suggester = SuggestFromList(list(filter(None, suggested_values))) 41 | self.input_widget = Input( 42 | password=argument.password, 43 | suggester=suggester, 44 | id="user_input", 45 | placeholder="value", 46 | ) 47 | 48 | if argument.multi and argument.value and isinstance(argument.value, list): 49 | self.tags.add_tag(*argument.value) 50 | elif argument.value: 51 | self.input_widget.value = argument.value 52 | 53 | def on_mount(self): 54 | if self.with_history: 55 | self.shelf = shelve.open(C.HISTORY_FILE, writeback=True) 56 | self.history = History(values=self.shelf.get(self.argument.name, dict())) 57 | 58 | def on_unmount(self): 59 | if self.with_history: 60 | self.shelf[self.argument.name] = self.history.values 61 | self.shelf.close() 62 | 63 | def set_value(self, value: str): 64 | self.input_widget.value = value 65 | 66 | def compose(self) -> ComposeResult: 67 | yield self.input_widget 68 | if self.argument.multi: 69 | yield self.tags 70 | 71 | def focus(self, *args, **kwargs): 72 | self.input_widget.focus(*args, **kwargs) 73 | 74 | def on_input_submitted(self, event: Input.Submitted): 75 | if self.input_widget.value and self.with_history: 76 | self.history.add(self.input_widget.value) 77 | 78 | if self.argument.multi: 79 | if not self.input_widget.value: 80 | self.post_message(self.Submitted(self.tags.values)) 81 | else: 82 | self.tags.add_tag(self.input_widget.value) 83 | self.input_widget.value = "" 84 | self.input_widget.focus() 85 | else: 86 | self.post_message(self.Submitted(self.input_widget.value)) 87 | event.stop() 88 | 89 | def action_cancel(self) -> None: 90 | self.input_widget.value = "" 91 | self.post_message(self.Submitted(None)) 92 | -------------------------------------------------------------------------------- /yakari/widgets/command_runner.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import List 3 | 4 | from rich.text import Text 5 | from textual import work 6 | from textual.app import ComposeResult 7 | from textual.reactive import reactive 8 | from textual.widget import Widget 9 | from textual.widgets import Input, RichLog 10 | 11 | 12 | class CommandRunner(Widget): 13 | can_focus_children = True 14 | 15 | _process_running: reactive(bool) = reactive(False, recompose=True) 16 | 17 | BINDINGS = [ 18 | ("ctrl+q", "terminate_subprocess", "terminate command"), 19 | ] 20 | 21 | def __init__(self): 22 | super().__init__() 23 | self.log_widget = RichLog(highlight=True, wrap=True, auto_scroll=True) 24 | self.log_widget.can_focus = False 25 | self.user_input = Input(placeholder="Interact with your command") 26 | self.subprocess: asyncio.subprocess.Process | None = None 27 | self.extra_stdout: bytes = b"" 28 | self.extra_stdout_lock = asyncio.Lock() 29 | 30 | @work 31 | async def start_subprocess(self, command: List[str]): 32 | """Start the subprocess and stream its output.""" 33 | self.log_widget.write(Text(f"$> {' '.join(command)}")) 34 | try: 35 | # Start the subprocess 36 | self._process_running = True 37 | self.subprocess = await asyncio.create_subprocess_exec( 38 | *command, 39 | stdout=asyncio.subprocess.PIPE, 40 | stdin=asyncio.subprocess.PIPE, 41 | stderr=asyncio.subprocess.PIPE, 42 | ) 43 | 44 | # Stream the subprocess output 45 | asyncio.create_task(self.stream_stdout(self.subprocess.stdout)) 46 | asyncio.create_task(self.stream_stderr(self.subprocess.stderr)) 47 | asyncio.create_task(self.extra_stdout_watcher()) 48 | 49 | # Wait for the process to finish and display the final message once 50 | return_code = await self.subprocess.wait() 51 | 52 | if self._process_running: # Only show this once 53 | self.log_widget.write( 54 | Text( 55 | f"[Command finished ({return_code})]\n", 56 | style="red" if return_code else "green", 57 | ) 58 | ) 59 | self._process_running = False 60 | 61 | self.subprocess = None 62 | except Exception as e: 63 | self.log_widget.write(Text(f"Error: {e}")) 64 | 65 | async def extra_stdout_watcher(self): 66 | SLEEP = 0.1 67 | 68 | while self.subprocess is not None: 69 | async with self.extra_stdout_lock: 70 | extra_before = self.extra_stdout 71 | 72 | await asyncio.sleep(SLEEP) 73 | 74 | async with self.extra_stdout_lock: 75 | extra_after = self.extra_stdout 76 | if extra_before and extra_before == extra_after: 77 | self.log_widget.write(Text(self.extra_stdout.decode())) 78 | self.extra_stdout = b"" 79 | 80 | async def stream_stderr(self, stream): 81 | while not stream.at_eof(): 82 | payload = await stream.readline() 83 | self.log_widget.write(Text(payload.decode(), style="red")) 84 | 85 | async def stream_stdout(self, stream): 86 | """Continuously read lines from the given stream and display them.""" 87 | READSIZE = 2048 88 | 89 | while not stream.at_eof(): 90 | payload = await stream.read(READSIZE) 91 | payload, *extra = payload.rsplit(b"\n", 1) 92 | 93 | async with self.extra_stdout_lock: 94 | payload = self.extra_stdout + payload 95 | self.log_widget.write(Text(payload.decode().strip())) 96 | 97 | if extra: 98 | async with self.extra_stdout_lock: 99 | self.extra_stdout = extra[0] 100 | else: 101 | payload = b"" 102 | async with self.extra_stdout_lock: 103 | self.extra_stdout = b"" 104 | 105 | async def send_input(self, user_input: str): 106 | """Send user input to the subprocess.""" 107 | if self.subprocess and self.subprocess.stdin: 108 | self.subprocess.stdin.write(user_input.encode() + b"\n") 109 | await self.subprocess.stdin.drain() 110 | self.log_widget.write(f"\nU> {user_input}") 111 | 112 | async def action_terminate_subprocess(self): 113 | if self.subprocess: 114 | self.subprocess.terminate() 115 | await self.subprocess.wait() 116 | 117 | async def on_input_submitted(self, event: Input.Submitted): 118 | """Handle input submission.""" 119 | user_input = self.user_input.value 120 | await self.send_input(user_input) 121 | self.user_input.value = "" 122 | 123 | def write(self, *args, **kwargs): 124 | self.log_widget.write(*args, **kwargs) 125 | 126 | def compose(self) -> ComposeResult: 127 | if self._process_running: 128 | yield self.user_input 129 | self.user_input.focus() 130 | yield self.log_widget 131 | 132 | async def on_unmount(self): 133 | await self.action_terminate_subprocess() 134 | -------------------------------------------------------------------------------- /yakari/widgets/footer.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.containers import Horizontal 3 | from textual.widgets import Footer as BaseFooter 4 | from textual.widgets import Label 5 | 6 | 7 | class Footer(BaseFooter): 8 | def _get_full_input(self) -> str: 9 | inputs = [] 10 | for screen in self.app.screen_stack: 11 | if hasattr(screen, "cur_input"): 12 | inputs.append(screen.cur_input) 13 | return inputs 14 | 15 | def compose(self) -> ComposeResult: 16 | if not self._bindings_ready: 17 | return 18 | 19 | inputs = self._get_full_input() 20 | 21 | yield Label(" > ".join(inputs), id="cur-input") 22 | 23 | labels = [Label("Shortcuts:", classes="title")] 24 | bindings = [ 25 | binding 26 | for (_, binding, enabled, tooltip) in self.app.active_bindings.values() 27 | if binding.show 28 | ] 29 | for binding in bindings: 30 | labels.append(Label(f"({binding.key})", classes="help")) 31 | labels.append(Label(binding.description)) 32 | yield Horizontal(*labels, id="help-section") 33 | 34 | edit_mode = getattr(self.app.screen, "edit_mode", None) 35 | hint = "edit" if edit_mode else "toggle" 36 | yield Label(f"Mode: {hint}", id="hint-edit") 37 | -------------------------------------------------------------------------------- /yakari/widgets/suggestions.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.message import Message 3 | from textual.widget import Widget 4 | from textual.widgets import OptionList as BaseOptionList 5 | from textual.widgets.option_list import Option, Separator 6 | 7 | 8 | class OptionList(BaseOptionList): 9 | BINDINGS = [("enter", "select", "select")] 10 | pass 11 | 12 | 13 | class SuggestionsWidget(Widget): 14 | class SuggestionSelected(Message): 15 | def __init__(self, value: str): 16 | self.value = value 17 | super().__init__() 18 | 19 | def __init__(self, suggested_values: list[str], *args, **kwargs): 20 | super().__init__(*args, **kwargs) 21 | self.suggested_values = list(filter(None, suggested_values)) 22 | fmt_suggested_values = [ 23 | Option(value) if value is not None else Separator() 24 | for value in suggested_values 25 | ] 26 | 27 | self.suggestions_widget = OptionList( 28 | *fmt_suggested_values, 29 | id="suggestions", 30 | ) 31 | self.suggestions_widget.border_title = "Suggested values" 32 | 33 | def on_option_list_option_selected(self, message: OptionList.OptionSelected): 34 | value = message.option.prompt 35 | self.post_message(self.SuggestionSelected(value)) 36 | message.stop() 37 | 38 | def compose(self) -> ComposeResult: 39 | yield self.suggestions_widget 40 | -------------------------------------------------------------------------------- /yakari/widgets/tags.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.containers import ScrollableContainer 3 | from textual.message import Message 4 | from textual.reactive import reactive 5 | from textual.widget import Widget 6 | from textual.widgets import Button, Label 7 | 8 | 9 | class Tag(Widget): 10 | can_focus = True 11 | can_focus_children = False 12 | 13 | value: reactive[str] = reactive(str, recompose=True) 14 | 15 | BINDINGS = [ 16 | ("backspace", "delete_this", "delete"), 17 | ] 18 | 19 | class Deleted(Message): 20 | def __init__(self, tag): 21 | super().__init__() 22 | self.tag = tag 23 | 24 | def __init__(self, value: str): 25 | super().__init__() 26 | self.value = value 27 | 28 | def compose(self) -> ComposeResult: 29 | yield Button("X") 30 | yield Label(self.value) 31 | 32 | def delete(self): 33 | self.post_message(Tag.Deleted(self)) 34 | 35 | def on_button_pressed(self, event: Button.Pressed): 36 | self.delete() 37 | 38 | def action_delete_this(self): 39 | self.delete() 40 | 41 | 42 | class TagsCollection(Widget): 43 | can_focus = False 44 | can_focus_children = True 45 | 46 | tags: reactive[list[Tag]] = reactive(default=list, recompose=True) 47 | 48 | def _sanitize_tag(self, tag: str | Tag) -> Tag: 49 | if isinstance(tag, str): 50 | tag = Tag(value=tag) 51 | return tag 52 | 53 | def __init__(self, tags: list[str | Tag] | None = None): 54 | super().__init__() 55 | if self.tags: 56 | self.tags = [self._sanitize_tag(tag) for tag in tags] 57 | self.mutate_reactive(TagsCollection.tags) 58 | 59 | def add_tag(self, *tags: str | Tag): 60 | for tag in tags: 61 | tag = self._sanitize_tag(tag) 62 | self.tags.append(tag) 63 | self.mutate_reactive(TagsCollection.tags) 64 | 65 | def delete_tag(self, tag: str | Tag): 66 | tag = self._sanitize_tag(tag) 67 | self.tags.remove(tag) 68 | self.mutate_reactive(TagsCollection.tags) 69 | 70 | def on_tag_deleted(self, message: Tag.Deleted): 71 | self.delete_tag(message.tag) 72 | 73 | def compose(self) -> ComposeResult: 74 | if self.tags: 75 | scrollable = ScrollableContainer(*self.tags) 76 | scrollable.can_focus = False 77 | yield scrollable 78 | 79 | @property 80 | def values(self): 81 | return [tag.value for tag in self.tags] 82 | --------------------------------------------------------------------------------