diff --git a/CLAUDE.md b/CLAUDE.md index 984b564..7a4753f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,8 +67,8 @@ Module-level `@tool` handlers. Call `set_workspace_root()` before use. - **`workspace`**: path resolve under root; path substring denylist; command basename denylist; clearer deny messages. - **`file`**: `read_file`, `write_file`, `listdir`, `tree`, `glob_paths`, `grep_files` (regex, skip VCS/binary), `edit_replace`, `edit_lineno` (1-based range), `copy_path` / `move_path` / `delete_path`. -- **`process`**: `run_command` (argv, no shell, timeout, optional stdin/env); PTY `open_pty` / `read_pty` / `write_pty` / `close_pty` (**Unix only**: `pty`+`fork`). -- PTY: structured status (`alive`/`exit_code`/`data`); `read_pty(..., until=)`; session limit/idle TTL/output budget; close SIGTERM→SIGKILL. +- **`process`**: `run_command` (argv, no shell, timeout, optional stdin/env); PTY `open_pty` / `read_pty` / `write_pty` / `close_pty` (POSIX: `pty`+`fork`; Windows: ConPTY via `pywinpty` env marker dep). +- PTY: backend in `pty_backend.py`; structured status (`alive`/`exit_code`/`data`); `read_pty(..., until=)`; session limit/idle TTL/output budget; close terminate→kill. - CLI limit hooks: interactive confirm to raise tool-loop rounds, PTY session cap, or PTY output budget. - Destructive confirms: `classify_danger` + `ToolRegistry(on_confirm=…)`; CLI default deny; config `confirm_destructive` / `path_denylist`. - **`vcs`**: read-only VCS tools (`vcs_kind` / `vcs_status` / `vcs_diff` / `vcs_log` / `vcs_branch`) via `VcsBackend` protocol; **git** implemented; detectors are pluggable for other systems. diff --git a/README.md b/README.md index 668d648..45a2dfa 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ User messages are saved immediately. On API error or Ctrl+C, partial assistant/t ## Tools (when enabled) -Default registry: file ops, `run_command` / PTY (Unix), read-only VCS (git), and human prompts (`ask_user` / `choose_user` / `form_user`). +Default registry: file ops, `run_command` / PTY (POSIX openpty; Windows ConPTY via pywinpty), read-only VCS (git), and human prompts (`ask_user` / `choose_user` / `form_user`). Safety defaults: diff --git a/pdm.lock b/pdm.lock index 7489e8a..fe5436c 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:cd5131c309485626148d1933a2a557a9cb6a41038b980bc26a453def4c38032c" +content_hash = "sha256:f244597f4e94cc6f75cb0e08bf337a7f09e7cc4e9444726be86e488247350a49" [[metadata.targets]] requires_python = "~=3.14" @@ -52,45 +52,39 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.7" +version = "3.4.9" requires_python = ">=3.7" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." groups = ["default"] files = [ - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501"}, + {file = "charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5"}, + {file = "charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b"}, ] [[package]] @@ -259,7 +253,7 @@ files = [ [[package]] name = "niquests" -version = "3.20.0" +version = "3.20.1" requires_python = ">=3.7" summary = "Niquests is a simple, yet elegant, HTTP library. It is a drop-in replacement for Requests, which is under feature freeze." groups = ["default"] @@ -269,8 +263,8 @@ dependencies = [ "wassima<3,>=1.0.1; sys_platform != \"emscripten\"", ] files = [ - {file = "niquests-3.20.0-py3-none-any.whl", hash = "sha256:86281a62314d7395f049d748480589e273dc1f429953afbac377fff142860429"}, - {file = "niquests-3.20.0.tar.gz", hash = "sha256:dba85ce23ac5052f0a1e1c1cf1c017511a993f906686e8b6c84e0b54818fd1dc"}, + {file = "niquests-3.20.1-py3-none-any.whl", hash = "sha256:71062880739c94d67aead1f8b7972baad3236b97a55a490493729789d1639fc4"}, + {file = "niquests-3.20.1.tar.gz", hash = "sha256:55951812cf997963bfef8e4f797ff0460ca571e53ec184039b010e12152c0e41"}, ] [[package]] @@ -374,76 +368,91 @@ files = [ {file = "pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42"}, ] +[[package]] +name = "pywinpty" +version = "3.0.5" +requires_python = ">=3.10" +summary = "Pseudo terminal support for Windows from Python." +groups = ["default"] +marker = "sys_platform == \"win32\"" +files = [ + {file = "pywinpty-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:03bb3c16d691d9242267201830bcd0e64a9b663170e9042bc84b210da9de15ac"}, + {file = "pywinpty-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:89c5c6ef08997a3b4b277b214a35fe15cab4dd6d119f0140aa71df5b1168fdbc"}, + {file = "pywinpty-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:7b566165e0c5fdd6abe167a5ac8b954be6a843eb55a85946576d6bc1dea03d6d"}, + {file = "pywinpty-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:24366280a8aa677323da87bec729cb3ea3b35367386cece0978bdc6e4695c690"}, + {file = "pywinpty-3.0.5.tar.gz", hash = "sha256:61db0db063de9865adbea66db294628f8577f608d9764a4c7d3384eeacc4e81b"}, +] + [[package]] name = "qh3" -version = "1.9.2" +version = "1.9.4" requires_python = ">=3.7" summary = "A lightway and fast implementation of QUIC and HTTP/3" groups = ["default"] marker = "(platform_system == \"Darwin\" or platform_system == \"Windows\" or platform_system == \"Linux\") and platform_python_implementation == \"PyPy\" and (platform_machine == \"x86_64\" or platform_machine == \"s390x\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"ppc64\" or platform_machine == \"AMD64\" or platform_machine == \"aarch64\" or platform_machine == \"arm64\" or platform_machine == \"ARM64\" or platform_machine == \"x86\" or platform_machine == \"i686\" or platform_machine == \"riscv64\" or platform_machine == \"riscv64gc\") and python_version < \"3.12\" or (platform_system == \"Darwin\" or platform_system == \"Windows\" or platform_system == \"Linux\") and python_full_version > \"3.7.10\" and (platform_machine == \"x86_64\" or platform_machine == \"s390x\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"ppc64\" or platform_machine == \"AMD64\" or platform_machine == \"aarch64\" or platform_machine == \"arm64\" or platform_machine == \"ARM64\" or platform_machine == \"x86\" or platform_machine == \"i686\" or platform_machine == \"riscv64\" or platform_machine == \"riscv64gc\") and platform_python_implementation == \"CPython\"" files = [ - {file = "qh3-1.9.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e38d88f41c6855184a53a1e583d56ae5163b8b70c7e2dc273889ec303cc27585"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3beebd98b08bd2fe951ae83b6e52dee3821158ab21ba05f15fbdb401bd6e19f9"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6afe20084235ff3c20256c952059277b5a0f5144d63d1892d1dfb5d1b635faef"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c8f2fdf124acef06306ee50421ee80d37dbc7dfd41ddcec2bb63f3e2508bd74"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:adcb00af8c4fddbb266ae4155ec5932dcb0a27dc14d7f7a27fad6a6dce9efd5e"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcbf94d8ab4223873f2fc2f7a0992ee0dd557f0ab5bce1ca12314e60a1d5cd39"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13b458a440f91d5336dd65f4a624b7eeac0ce1b2a8d02e250df32470606c6775"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7b0429d9c5d892633ee822a428c840201a2ef53a16cec4d4aeb1ae08146cae9"}, - {file = "qh3-1.9.2-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:d4ab482b9f12abb5fc6fc67a214431037d094b38673efd6d0df5d64338dd5351"}, - {file = "qh3-1.9.2-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:ab254e2289bfc949b0177aa7739ac963f54355716aee39966120ca71d7315aeb"}, - {file = "qh3-1.9.2-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:9a6aa7146a62c88fbc8adfa6dcae435670a8487e5f3bb00f92a29060cd630102"}, - {file = "qh3-1.9.2-cp314-cp314t-musllinux_1_1_i686.whl", hash = "sha256:2710e359d9320014a33149853cbc8c4bd9ca83181bb7a896c7704a1e429c27c4"}, - {file = "qh3-1.9.2-cp314-cp314t-musllinux_1_1_riscv64.whl", hash = "sha256:4d136fa78171ffc094e06c25361fe00482accdb406a442ce93e8121314880659"}, - {file = "qh3-1.9.2-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a48cc6b4d21ef85071ef2f20ba27d7412827ff8385baa19d14b0c58e44fdbc57"}, - {file = "qh3-1.9.2-cp314-cp314t-win32.whl", hash = "sha256:c4dc40c1ac0f31bfc0dadeaf02578460c7456498f549bb6ae742883f90286243"}, - {file = "qh3-1.9.2-cp314-cp314t-win_amd64.whl", hash = "sha256:19893cfe25a749c363e81415dcdb3b027707e71a09b0e1d4f1e3098d7e5975d3"}, - {file = "qh3-1.9.2-cp314-cp314t-win_arm64.whl", hash = "sha256:75b587b26a8cdeb1c715e5d2ec08d4987be8e02bb76a6b24a4075293ade0bd3d"}, - {file = "qh3-1.9.2-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aabb7c86f49c668ab5bfb297c5da18755a3ca9cf362f8ce73b86f55f43f662fc"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e632d420d88e30e4e3fc9f5457c362e964e93e91f9c90d148b68b0d4b0dc06"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5260ff1c42f7221fb7e92bc9c55a009341c2851f2dbb28475ddcf0a32c2226a"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:304e81b4cf3f97610a1aceaa1aec90a97f0c700071efcc9d6a3b86da0b57539d"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:aa20581d3daf8f80b62f42a10c1f69c12f3b6310f141a2e2f4259de588e570fe"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd3a3d7d737644a4d24117613e89220cd5223cb1e0c8281a5a65a97ebaf1efea"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82edac5bc2eea0881baa142d93e80469347875909acc19ce5e4236b50c465f8e"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b18b9d2f892bb95f7f3d3428bb4166f974c9a2e26fcf91b33dda9b4285e7928"}, - {file = "qh3-1.9.2-cp37-abi3-manylinux_2_39_riscv64.whl", hash = "sha256:06f2a10a01b71137fef8bb7b4115f1016d6e4a7f056ce41517ff157e808bf444"}, - {file = "qh3-1.9.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e15519dc326315c52ab708028bf72aa9162eb3c1bd0ba130dc6d371b000639d2"}, - {file = "qh3-1.9.2-cp37-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:7e04bf8bbbfb8a9f363fd38834aa9c52fcc0164182c72ea10cf30a3e2df066c9"}, - {file = "qh3-1.9.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:e3b0b0d17268d57b808a658db7fd7c5e62561ad6d462949d8466c92b22551067"}, - {file = "qh3-1.9.2-cp37-abi3-musllinux_1_1_riscv64.whl", hash = "sha256:efe5ad0a9df71a9a5881615b0c0888767ad3a7cab05e54ba98db52d89d0b796a"}, - {file = "qh3-1.9.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:742dd7c5e2207db27d24036ca599cad056bd5e58a5ce1b43f0c424df149e17d3"}, - {file = "qh3-1.9.2-cp37-abi3-win32.whl", hash = "sha256:8d1f185a76578929b0de3dcef135c0cc21d4bff0ef00bd75127d244ba3426125"}, - {file = "qh3-1.9.2-cp37-abi3-win_amd64.whl", hash = "sha256:643e8aa05519dddaf20159d1c13c15473de7649622a1c438968ce0f0da973951"}, - {file = "qh3-1.9.2-cp37-abi3-win_arm64.whl", hash = "sha256:87bace13780b3c2238db1dd31deb10152203d5b360ca6515c78b853796245606"}, - {file = "qh3-1.9.2.tar.gz", hash = "sha256:c6c92f63c2ec292256b5a5ed9345c42344bdaca2e55ec795623987a563aea19c"}, + {file = "qh3-1.9.4-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:100368855b0e103774438aaf8b38c711fb51b79f18e3b9dc873ea573648bcc3d"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9d45e544e0a556380bd2a92af180ee98d32bca6cf622f28350ebc000611bdf"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43f80d662078c26563a1aac32a4df22649473d996bf2422e073c8aa55b1c837c"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:551f60d487d849d6f2313dda94e54ebb16b1a29acb93676540ae790a56308c24"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3755d777ff35561bc719f571ebc4b91aaa8a945c210e50b2576bf2cc69cb511f"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04b64bb18e10d8f482bea1ef43e5da3448e2d5ba78bb0c735da5b9e02d7dfb0e"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b47597daa4ae9195f81a35822217a395d3277f01a393d9c2bb3796a6b458b22c"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9bb9012bc02c44da6af1b931b6082588519685fad8a1f866c341f749394f53f"}, + {file = "qh3-1.9.4-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:9c7f3781ec022dd958b5eb0e5cb2e2a54e207a3d8837fb7290bc239912648fd7"}, + {file = "qh3-1.9.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:633a24ab2db76d64881876c9d0f1b226a47d892bd7d78e61ea2c788ab59570af"}, + {file = "qh3-1.9.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:f2ff36b84a4ab9b68806af1f6985fa3b47e29695effe766a3e70c21efac8157c"}, + {file = "qh3-1.9.4-cp314-cp314t-musllinux_1_1_i686.whl", hash = "sha256:2100697d736dbdfd1d3f0266c9fcadea6230b66ed59e3e7f8c9a9d1a6eec0c65"}, + {file = "qh3-1.9.4-cp314-cp314t-musllinux_1_1_riscv64.whl", hash = "sha256:9ce179efaf97f927f01fe158619f5624d8d874c7161b7c497f06897cdde210d5"}, + {file = "qh3-1.9.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:476ee4fcdbd3454eb0eb4dee5678294f06c08ca8e8b83160e2d0ae3c4ae34603"}, + {file = "qh3-1.9.4-cp314-cp314t-win32.whl", hash = "sha256:fe9df8136bf358d45a2757287523abaeb208fcbe2c1e34664562eb2d8f64dbdb"}, + {file = "qh3-1.9.4-cp314-cp314t-win_amd64.whl", hash = "sha256:1fbdf53feb1732faf6c1cb34f2e1cbe4b543054db7b4d19d8752f1162e9370be"}, + {file = "qh3-1.9.4-cp314-cp314t-win_arm64.whl", hash = "sha256:e03ee1e2157a31c69415c3dbfbfe64e02469499ca46e968d13d8a7b289bf520a"}, + {file = "qh3-1.9.4-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:26f7c8ed1a7ac0e25b654f030a4f472830c80890cc284dc40a31c17ecc21fbf0"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57b3dfe5dd2da2084adea63dba0bf587a4096ebf9207339187c90139d1f460d"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7fa1a3debb5199da3c7f45b0ca6efa55174dfd6229370e774099073143ebe9e9"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dc9ff40d3a06f27b47d93417666f03ba6fd6b9585d9711ebbddf84fa4b7c50f"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:010b132c33719af3df4c1d6f569e52a2a20046c55a31c5532e741019c8c53868"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:124045b0c04c05afc038a239d7717019b4c4b9d0cb967040b4331f4d588eff7d"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a94a1726bf46a451a0295d68fafde28794e83ab6fda7457cbd9309c38151de9"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b361e627b6a529ecb231c1d5f3f2abd6d1952b0d7cc02c7718d9657d55dae37"}, + {file = "qh3-1.9.4-cp37-abi3-manylinux_2_39_riscv64.whl", hash = "sha256:43b6cfb2240836e3e54bbe61ad1b8fa5716516ff5359131a2421c55676a24f26"}, + {file = "qh3-1.9.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e6db3def610e9c2d2f3602d3e9c8b2156273225f6eedf74a4749d3bc21af8167"}, + {file = "qh3-1.9.4-cp37-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:22d5879dc93d2c7ee14cc5933b12c7780d9c7705dff4553becea152fcb767d99"}, + {file = "qh3-1.9.4-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:2c2b2005fe6c605d61cac05ec2b3c019cc546184fa43444f3d1e3d22ed020ab2"}, + {file = "qh3-1.9.4-cp37-abi3-musllinux_1_1_riscv64.whl", hash = "sha256:e991b0f20b172fea466d0ffd499a85bf2f26decdeeda537275c72b5f7b26ccfe"}, + {file = "qh3-1.9.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c13775ed27a88b297f6a1aa05ffb286879453f99e1e53e0af2b22e7ed8f92ebf"}, + {file = "qh3-1.9.4-cp37-abi3-win32.whl", hash = "sha256:952abe1be90c0114993886792a8a5ec2211f8391df41227f5b7a5a02ff71bc26"}, + {file = "qh3-1.9.4-cp37-abi3-win_amd64.whl", hash = "sha256:5543cb40a48d401f3fa4ec89398e3a213634f195716a9d06d70417c05edbbb32"}, + {file = "qh3-1.9.4-cp37-abi3-win_arm64.whl", hash = "sha256:d0bd73b3e73f4f80d2430d410b532222ecf57e9d4fe8600b7683c775cbff9479"}, + {file = "qh3-1.9.4.tar.gz", hash = "sha256:bd2ea9baf19656c544a48a56a195f2ac257cd973b566f5f2998fa3b7446281a1"}, ] [[package]] name = "ruff" -version = "0.15.20" +version = "0.15.21" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["dev"] files = [ - {file = "ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078"}, - {file = "ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b"}, - {file = "ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632"}, - {file = "ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd"}, - {file = "ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b"}, - {file = "ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267"}, - {file = "ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c"}, - {file = "ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae"}, - {file = "ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b"}, - {file = "ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487"}, - {file = "ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3"}, - {file = "ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053"}, - {file = "ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4"}, - {file = "ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460"}, - {file = "ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21"}, - {file = "ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415"}, - {file = "ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca"}, - {file = "ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566"}, + {file = "ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d"}, + {file = "ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd"}, + {file = "ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646"}, + {file = "ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be"}, + {file = "ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd"}, + {file = "ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41"}, + {file = "ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86"}, + {file = "ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67"}, + {file = "ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8"}, + {file = "ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075"}, + {file = "ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341"}, + {file = "ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d"}, + {file = "ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233"}, + {file = "ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f"}, + {file = "ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd"}, + {file = "ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3"}, + {file = "ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8"}, + {file = "ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500"}, ] [[package]] @@ -500,7 +509,7 @@ files = [ [[package]] name = "urllib3-future" -version = "2.22.900" +version = "2.22.901" requires_python = ">=3.7" summary = "urllib3.future is a powerful HTTP 1.1, 2, and 3 client with both sync and async interfaces" groups = ["default"] @@ -510,18 +519,18 @@ dependencies = [ "qh3<2.0.0,>=1.5.4; (platform_system == \"Darwin\" or platform_system == \"Windows\" or platform_system == \"Linux\") and platform_python_implementation == \"PyPy\" and (platform_machine == \"x86_64\" or platform_machine == \"s390x\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"ppc64\" or platform_machine == \"AMD64\" or platform_machine == \"aarch64\" or platform_machine == \"arm64\" or platform_machine == \"ARM64\" or platform_machine == \"x86\" or platform_machine == \"i686\" or platform_machine == \"riscv64\" or platform_machine == \"riscv64gc\") and python_version < \"3.12\" or (platform_system == \"Darwin\" or platform_system == \"Windows\" or platform_system == \"Linux\") and python_full_version > \"3.7.10\" and (platform_machine == \"x86_64\" or platform_machine == \"s390x\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"ppc64\" or platform_machine == \"AMD64\" or platform_machine == \"aarch64\" or platform_machine == \"arm64\" or platform_machine == \"ARM64\" or platform_machine == \"x86\" or platform_machine == \"i686\" or platform_machine == \"riscv64\" or platform_machine == \"riscv64gc\") and platform_python_implementation == \"CPython\"", ] files = [ - {file = "urllib3_future-2.22.900-py3-none-any.whl", hash = "sha256:803976fe865f03d61d5ddc17dbdbac2bacc08202aad303f282cc4af319ddbfd2"}, - {file = "urllib3_future-2.22.900.tar.gz", hash = "sha256:3161900c00cf41db90dd3d938c1d14960faab475dd6ca0bf5ecd06112327c714"}, + {file = "urllib3_future-2.22.901-py3-none-any.whl", hash = "sha256:dc2d4572ba612be69e3f6db87d1f469b7d08f107e80fd8749081d7e6a5a2fe10"}, + {file = "urllib3_future-2.22.901.tar.gz", hash = "sha256:fbece0ff51299a213c926897ffbed61d820ab5ef2ba81cece361c049fe2f51a9"}, ] [[package]] name = "wassima" -version = "2.1.1" +version = "2.1.2" requires_python = ">=3.7" summary = "Access your OS root certificates with utmost ease" groups = ["default"] marker = "sys_platform != \"emscripten\"" files = [ - {file = "wassima-2.1.1-py3-none-any.whl", hash = "sha256:ab0f12c091ff697f111eea5f925aafd83736909a1c9c764a8bbf874b2e4d4a42"}, - {file = "wassima-2.1.1.tar.gz", hash = "sha256:9c6ad4aa3cfbe91fd75f9eae315ba563bbc7d9d2479aef0c288fa7f1ca3b0c53"}, + {file = "wassima-2.1.2-py3-none-any.whl", hash = "sha256:9a5779e8ade39960973c6cc17e095cc998d6345ab3dbdd1ec2d7fde8c356fb82"}, + {file = "wassima-2.1.2.tar.gz", hash = "sha256:f74b5441151728c54118ece6d747cfe92b2c595a3d062a1955f42a2bc894cd10"}, ] diff --git a/pyproject.toml b/pyproject.toml index d846e12..477c8eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,8 @@ dependencies = [ "sqlalchemy>=2.0.51", "aiosqlite>=0.22.1", "click>=8.4.2", - "awaitlet>=0.0.1" + "awaitlet>=0.0.1", + "pywinpty>=2.0.0; sys_platform == \"win32\"" ] requires-python = ">=3.14,<4" readme = "README.md" @@ -38,6 +39,8 @@ build-backend = "pdm.backend" [tool.basedpyright] typeCheckingMode = "recommended" strictGenericNarrowing = true +# winpty is Windows-only (pywinpty); stubs live under typings/ (pyright default stubPath). +reportMissingModuleSource = "hint" reportAny = "hint" reportExplicitAny = "hint" reportImplicitStringConcatenation = "hint" @@ -60,6 +63,10 @@ reportUnusedParameter = "none" reportMissingImports = "none" reportMissingTypeStubs = "none" +[[tool.basedpyright.executionEnvironments]] +root = "scripts" +reportUnusedCallResult = "none" + [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" @@ -163,3 +170,7 @@ allowed-confusables = [ "TRY003", # long exception messages in tests "FBT", # boolean positional args in helpers ] +"scripts/**" = [ + "T201", # CLI scripts print status + "PLR0911", # command dispatch returns +] diff --git a/src/plyngent/tools/process/open_pty.py b/src/plyngent/tools/process/open_pty.py index 922393e..8611e0e 100644 --- a/src/plyngent/tools/process/open_pty.py +++ b/src/plyngent/tools/process/open_pty.py @@ -10,10 +10,11 @@ from .pty_session import PtyManager @tool def open_pty(command: list[str], *, cwd: str = ".") -> str: - """Open a Unix PTY session running ``command`` (argv) under the workspace. + """Open a PTY session running ``command`` (argv) under the workspace. - Returns structured status including session_id. Not supported on Windows. - Failed exec surfaces via later read_pty data (marker) and exit_code=127. + POSIX uses openpty/fork; Windows uses ConPTY (pywinpty). Returns structured + status including session_id. Failed exec may surface via later read_pty + data (marker) and a non-zero exit_code. """ try: session = PtyManager.open(command, cwd=cwd) diff --git a/src/plyngent/tools/process/pty_backend.py b/src/plyngent/tools/process/pty_backend.py new file mode 100644 index 0000000..7eb0a81 --- /dev/null +++ b/src/plyngent/tools/process/pty_backend.py @@ -0,0 +1,287 @@ +"""Platform PTY backends (POSIX openpty/fork; Windows ConPTY via pywinpty).""" + +from __future__ import annotations + +import contextlib +import os +import select +import signal +import sys +import time +from dataclasses import dataclass +from typing import TYPE_CHECKING, Protocol, runtime_checkable + +if TYPE_CHECKING: + from pathlib import Path + + +@runtime_checkable +class PtyHandle(Protocol): + """One live PTY-backed child process.""" + + @property + def master_fd(self) -> int | None: + """POSIX master FD when applicable; ``None`` on Windows ConPTY.""" + ... + + @property + def pid(self) -> int | None: + """POSIX child pid when applicable; ``None`` on Windows ConPTY.""" + ... + + def poll_exit(self) -> tuple[bool, int | None]: + """Return ``(alive, exit_code)`` without blocking.""" + ... + + def read_bytes(self, max_bytes: int, timeout: float) -> bytes: + """Read up to *max_bytes* waiting up to *timeout* seconds.""" + ... + + def write_bytes(self, data: bytes) -> None: + """Write raw bytes to the PTY input.""" + ... + + def terminate(self) -> None: + """Request graceful shutdown (SIGTERM / TerminateProcess soft).""" + ... + + def kill(self) -> None: + """Force-kill the child.""" + ... + + def wait_reap(self, *, timeout: float | None = None) -> None: + """Wait for the child to exit and reap it.""" + ... + + def close_resources(self) -> None: + """Release OS handles/FDs (after terminate/kill).""" + ... + + +_EXEC_FAIL_MARKER = b"plyngent-pty-exec-failed: " +_STDERR_FD = 2 + + +@dataclass +class PosixPtyHandle: + master_fd: int + pid: int + _alive: bool = True + _exit_code: int | None = None + _closed: bool = False + + def poll_exit(self) -> tuple[bool, int | None]: + if not self._alive or self._closed: + return self._alive, self._exit_code + try: + waited_pid, status = os.waitpid(self.pid, os.WNOHANG) + except ChildProcessError: + self._alive = False + return False, self._exit_code + if waited_pid == 0: + return True, None + self._alive = False + if os.WIFEXITED(status): + self._exit_code = os.WEXITSTATUS(status) + elif os.WIFSIGNALED(status): + self._exit_code = -os.WTERMSIG(status) + else: + self._exit_code = status + return False, self._exit_code + + def read_bytes(self, max_bytes: int, timeout: float) -> bytes: + if self._closed or max_bytes < 1: + return b"" + data = b"" + try: + ready, _, _ = select.select([self.master_fd], [], [], timeout) + if ready: + data = os.read(self.master_fd, max_bytes) + except (OSError, ValueError): + data = b"" + _ = self.poll_exit() + return data + + def write_bytes(self, data: bytes) -> None: + _ = os.write(self.master_fd, data) + + def terminate(self) -> None: + if not self._alive: + return + with contextlib.suppress(ProcessLookupError): + os.kill(self.pid, signal.SIGTERM) + + def kill(self) -> None: + if not self._alive: + return + with contextlib.suppress(ProcessLookupError): + os.kill(self.pid, signal.SIGKILL) + with contextlib.suppress(ChildProcessError): + _ = os.waitpid(self.pid, 0) + self._alive = False + if self._exit_code is None: + self._exit_code = -signal.SIGKILL + + def wait_reap(self, *, timeout: float | None = None) -> None: + deadline = None if timeout is None else time.monotonic() + max(0.0, timeout) + while self._alive: + _ = self.poll_exit() + if not self._alive: + with contextlib.suppress(ChildProcessError): + _ = os.waitpid(self.pid, os.WNOHANG) + return + if deadline is not None and time.monotonic() >= deadline: + return + time.sleep(0.05) + + def close_resources(self) -> None: + if self._closed: + return + with contextlib.suppress(OSError): + os.close(self.master_fd) + self._closed = True + + +if sys.platform == "win32": + from winpty import PtyProcess + + @dataclass + class WinPtyHandle: + """ConPTY session via pywinpty ``PtyProcess``.""" + + process: PtyProcess + _alive: bool = True + _exit_code: int | None = None + _closed: bool = False + + @property + def master_fd(self) -> int | None: + return None + + @property + def pid(self) -> int | None: + return None + + def poll_exit(self) -> tuple[bool, int | None]: + if not self._alive or self._closed: + return self._alive, self._exit_code + try: + alive = bool(self.process.isalive()) + except Exception: # noqa: BLE001 — treat as dead on probe failure + alive = False + if alive: + return True, None + self._alive = False + status = self.process.exitstatus + if isinstance(status, int): + self._exit_code = status + return False, self._exit_code + + def read_bytes(self, max_bytes: int, timeout: float) -> bytes: + """Read up to *max_bytes*. + + pywinpty ``PtyProcess.read(size)`` is blocking and has no timeout kwarg; + *timeout* is accepted for API parity with the POSIX backend. + """ + del timeout # ConPTY read has no timed wait in pywinpty 3.x + if self._closed or max_bytes < 1: + return b"" + try: + raw = self.process.read(max_bytes) + except Exception: # noqa: BLE001 — EOF / closed PTY + _ = self.poll_exit() + return b"" + data = raw.encode("utf-8", errors="replace") + if len(data) > max_bytes: + data = data[:max_bytes] + _ = self.poll_exit() + return data + + def write_bytes(self, data: bytes) -> None: + text = data.decode("utf-8", errors="replace") + _ = self.process.write(text) + + def terminate(self) -> None: + if not self._alive: + return + with contextlib.suppress(Exception): + _ = self.process.terminate(force=False) + + def kill(self) -> None: + with contextlib.suppress(Exception): + _ = self.process.terminate(force=True) + with contextlib.suppress(Exception): + self.process.kill() + self._alive = False + if self._exit_code is None: + self._exit_code = 1 + + def wait_reap(self, *, timeout: float | None = None) -> None: + deadline = None if timeout is None else time.monotonic() + max(0.0, timeout) + while self._alive: + _ = self.poll_exit() + if not self._alive: + return + if deadline is not None and time.monotonic() >= deadline: + return + time.sleep(0.05) + + def close_resources(self) -> None: + if self._closed: + return + with contextlib.suppress(Exception): + self.process.close(force=True) + self._closed = True + + def pty_available() -> bool: + return True + + def spawn_pty(command: list[str], *, cwd: Path) -> PtyHandle: + if not command: + msg = "command argv must not be empty" + raise OSError(msg) + try: + process = PtyProcess.spawn(command, cwd=str(cwd)) + except Exception as exc: + msg = f"failed to open ConPTY: {exc}" + raise OSError(msg) from exc + return WinPtyHandle(process=process) + +else: + + def pty_available() -> bool: + return hasattr(os, "fork") + + def spawn_pty(command: list[str], *, cwd: Path) -> PtyHandle: + if not command: + msg = "command argv must not be empty" + raise OSError(msg) + return _spawn_posix(command, cwd=cwd) + + def _spawn_posix(command: list[str], *, cwd: Path) -> PosixPtyHandle: + import pty + + master_fd, slave_fd = pty.openpty() + with contextlib.suppress(OSError, AttributeError): + os.set_inheritable(master_fd, False) # noqa: FBT003 — stdlib API + pid = os.fork() + if pid == 0: # child — fork-then-exec only + try: + os.close(master_fd) + _ = os.setsid() + _ = os.dup2(slave_fd, 0) + _ = os.dup2(slave_fd, 1) + _ = os.dup2(slave_fd, _STDERR_FD) + if slave_fd > _STDERR_FD: + os.close(slave_fd) + _ = os.chdir(cwd) + os.execvp(command[0], command) + except OSError as exc: + with contextlib.suppress(OSError): + _ = os.write(1, _EXEC_FAIL_MARKER + str(exc).encode(errors="replace") + b"\n") + os._exit(127) + + os.close(slave_fd) + with contextlib.suppress(OSError, AttributeError): + os.set_inheritable(master_fd, False) # noqa: FBT003 — stdlib API + return PosixPtyHandle(master_fd=master_fd, pid=pid) diff --git a/src/plyngent/tools/process/pty_session.py b/src/plyngent/tools/process/pty_session.py index 0c7480e..6dc9635 100644 --- a/src/plyngent/tools/process/pty_session.py +++ b/src/plyngent/tools/process/pty_session.py @@ -1,17 +1,13 @@ -"""Unix PTY session registry (requires ``pty`` + ``os.fork``; not supported on Windows).""" +"""Cross-platform PTY session registry (POSIX openpty/fork; Windows ConPTY).""" from __future__ import annotations -import contextlib -import os -import pty -import select -import signal import time from dataclasses import dataclass, field from threading import Lock from typing import TYPE_CHECKING, ClassVar +from plyngent.tools.process.pty_backend import PtyHandle, pty_available, spawn_pty from plyngent.tools.workspace import WorkspaceError, check_command_allowed, resolve_path if TYPE_CHECKING: @@ -27,15 +23,12 @@ DEFAULT_SESSION_OUTPUT_BUDGET = 256_000 DEFAULT_CLOSE_GRACE_SECONDS = 0.5 _SESSION_LIMIT_STEP = 4 _BUDGET_STEP = 256_000 -_STDERR_FD = 2 -_EXEC_FAIL_MARKER = b"plyngent-pty-exec-failed: " @dataclass class PtySession: session_id: int - master_fd: int - pid: int + handle: PtyHandle closed: bool = False alive: bool = True exit_code: int | None = None @@ -45,6 +38,16 @@ class PtySession: output_budget: int = DEFAULT_SESSION_OUTPUT_BUDGET command: tuple[str, ...] = () + @property + def master_fd(self) -> int | None: + """POSIX master FD when available (``None`` on Windows ConPTY).""" + return self.handle.master_fd + + @property + def pid(self) -> int | None: + """POSIX child pid when available (``None`` on Windows ConPTY).""" + return self.handle.pid + @dataclass(frozen=True) class PtyReadResult: @@ -114,6 +117,9 @@ class PtyManager: *, cwd: str = ".", ) -> PtySession: + if not pty_available(): + msg = "PTY is not available on this platform (Windows needs pywinpty)" + raise WorkspaceError(msg) check_command_allowed(command) workdir = resolve_path(cwd) if not workdir.is_dir(): @@ -131,37 +137,18 @@ class PtyManager: msg = f"{reason}; close idle sessions or allow a higher limit" raise WorkspaceError(msg) - master_fd, slave_fd = pty.openpty() - # Parent must not pass master into the child (or future children). - with contextlib.suppress(OSError, AttributeError): - os.set_inheritable(master_fd, False) # noqa: FBT003 — stdlib API - pid = os.fork() - if pid == 0: # child — keep path fork-then-exec only (no Python after exec fail except _exit) - try: - os.close(master_fd) - _ = os.setsid() - _ = os.dup2(slave_fd, 0) - _ = os.dup2(slave_fd, 1) - _ = os.dup2(slave_fd, _STDERR_FD) - if slave_fd > _STDERR_FD: - os.close(slave_fd) - _ = os.chdir(workdir) - os.execvp(command[0], command) - except OSError as exc: - with contextlib.suppress(OSError): - _ = os.write(1, _EXEC_FAIL_MARKER + str(exc).encode(errors="replace") + b"\n") - os._exit(127) + try: + handle = spawn_pty(command, cwd=workdir) + except OSError as exc: + msg = f"failed to open PTY: {exc}" + raise WorkspaceError(msg) from exc - os.close(slave_fd) - with contextlib.suppress(OSError, AttributeError): - os.set_inheritable(master_fd, False) # noqa: FBT003 — stdlib API with cls._lock: session_id = cls._next_id cls._next_id += 1 session = PtySession( session_id=session_id, - master_fd=master_fd, - pid=pid, + handle=handle, command=tuple(command), output_budget=cls.session_output_budget, ) @@ -181,20 +168,10 @@ class PtyManager: def _poll_exit(cls, session: PtySession) -> None: if not session.alive or session.closed: return - try: - waited_pid, status = os.waitpid(session.pid, os.WNOHANG) - except ChildProcessError: - session.alive = False - return - if waited_pid == 0: - return - session.alive = False - if os.WIFEXITED(status): - session.exit_code = os.WEXITSTATUS(status) - elif os.WIFSIGNALED(status): - session.exit_code = -os.WTERMSIG(status) - else: - session.exit_code = status + alive, exit_code = session.handle.poll_exit() + session.alive = alive + if exit_code is not None: + session.exit_code = exit_code @classmethod def refresh(cls, session_id: int) -> PtySession: @@ -210,13 +187,7 @@ class PtyManager: if session.closed or (session.output_budget - session.bytes_read) <= 0: return b"" to_read = min(max_bytes, session.output_budget - session.bytes_read) - data = b"" - try: - ready, _, _ = select.select([session.master_fd], [], [], timeout) - if ready: - data = os.read(session.master_fd, to_read) - except OSError, ValueError: - data = b"" + data = session.handle.read_bytes(to_read, timeout) cls._poll_exit(session) if not data: return b"" @@ -336,7 +307,7 @@ class PtyManager: msg = f"PTY session process is not alive: {session_id}" raise WorkspaceError(msg) try: - _ = os.write(session.master_fd, data.encode()) + session.handle.write_bytes(data.encode()) except OSError as exc: cls._poll_exit(session) msg = f"failed to write PTY: {exc}" @@ -366,27 +337,19 @@ class PtyManager: cls._poll_exit(session) if session.alive: - with contextlib.suppress(ProcessLookupError): - os.kill(session.pid, signal.SIGTERM) - deadline = time.monotonic() + max(0.0, grace_seconds) - while session.alive and time.monotonic() < deadline: - time.sleep(0.05) - cls._poll_exit(session) + session.handle.terminate() + session.handle.wait_reap(timeout=max(0.0, grace_seconds)) + cls._poll_exit(session) if session.alive: - with contextlib.suppress(ProcessLookupError): - os.kill(session.pid, signal.SIGKILL) - with contextlib.suppress(ChildProcessError): - _ = os.waitpid(session.pid, 0) - session.alive = False + session.handle.kill() + session.handle.wait_reap(timeout=1.0) + cls._poll_exit(session) + if session.alive: + session.alive = False if session.exit_code is None: - session.exit_code = -signal.SIGKILL - else: - # Ensure reaped - with contextlib.suppress(ChildProcessError): - _ = os.waitpid(session.pid, os.WNOHANG) + session.exit_code = session.handle.poll_exit()[1] - with contextlib.suppress(OSError): - os.close(session.master_fd) + session.handle.close_resources() session.closed = True with cls._lock: _ = cls._sessions.pop(session_id, None) diff --git a/tests/test_tools/test_process.py b/tests/test_tools/test_process.py index 0e60be2..3f0b05c 100644 --- a/tests/test_tools/test_process.py +++ b/tests/test_tools/test_process.py @@ -246,12 +246,25 @@ async def test_pty_output_budget_is_per_session(workspace: object) -> None: def test_pty_master_not_inheritable(workspace: object) -> None: del workspace import os + import sys + + if sys.platform == "win32": + import pytest + + pytest.skip("master FD inheritance is POSIX-only") try: opened = call_sync(open_pty, ["sleep", "5"]) session_id = _session_id(opened) session = PtyManager.get(session_id) assert session is not None + assert session.master_fd is not None assert os.get_inheritable(session.master_fd) is False finally: PtyManager.close_all() + + +def test_pty_backend_available() -> None: + from plyngent.tools.process.pty_backend import pty_available + + assert pty_available() is True diff --git a/typings/winpty.pyi b/typings/winpty.pyi new file mode 100644 index 0000000..be8f9fd --- /dev/null +++ b/typings/winpty.pyi @@ -0,0 +1,30 @@ +"""Stubs for pywinpty (ConPTY) matching winpty.PtyProcess runtime API.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import Any + +class PtyProcess: + exitstatus: int | None + + @classmethod + def spawn( + cls, + argv: str | Sequence[str], + cwd: str | None = None, + env: dict[str, str] | None = None, + dimensions: tuple[int, int] = (24, 80), + backend: int | None = None, + ) -> PtyProcess: ... + def isalive(self) -> bool: ... + def read(self, size: int = 1024) -> str: ... + def write(self, s: str) -> int: ... + def terminate(self, force: bool = False) -> bool: ... + def kill(self, sig: int | None = None) -> None: ... + def close(self, force: bool = False) -> None: ... + def wait(self) -> int | None: ... + def fileno(self) -> int: ... + def isatty(self) -> bool: ... + def setwinsize(self, rows: int, cols: int) -> None: ... + def getwinsize(self) -> tuple[int, int]: ...