mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 05:55:16 +08:00
core/process: cross-platform PTY via backend and pywinpty
POSIX openpty/fork and Windows ConPTY; win32 marker dep plus typings stub.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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"},
|
||||
]
|
||||
|
||||
+12
-1
@@ -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
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
handle = spawn_pty(command, cwd=workdir)
|
||||
except OSError as exc:
|
||||
with contextlib.suppress(OSError):
|
||||
_ = os.write(1, _EXEC_FAIL_MARKER + str(exc).encode(errors="replace") + b"\n")
|
||||
os._exit(127)
|
||||
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)
|
||||
session.handle.terminate()
|
||||
session.handle.wait_reap(timeout=max(0.0, grace_seconds))
|
||||
cls._poll_exit(session)
|
||||
if session.alive:
|
||||
session.handle.kill()
|
||||
session.handle.wait_reap(timeout=1.0)
|
||||
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
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]: ...
|
||||
Reference in New Issue
Block a user