From 8d226fcffa9b79ac8f025046bdaf317229f3759b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Oct 2026 17:24:43 -0500 Subject: [PATCH 1/4] Server(docs[overview]): Group members by purpose why: The Server page opened with a hierarchy list and one sentence, so finding a member meant scanning all of them. It also never said that an empty sessions, clients, or attached_sessions list can mean tmux is unreachable. what: - Describe what a Server addresses, when it starts tmux, and that each list read runs a tmux command - Add a "What is on here" overview that groups every public member under a bold lead-in, linked with :meth: and :attr:, and ends on cmd - Point at is_alive and raise_if_dead to tell an empty list from a missing server - Add two Examples doctests: get with default=None, and an idle server reading as empty --- src/libtmux/server.py | 132 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index e650557c34..6be12ab82c 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -97,7 +97,117 @@ class Server( - :class:`Pane` - When instantiated stores information on live, running tmux server. + A :class:`Server` is the handle to one tmux server, named by its socket. + Creating one starts nothing and contacts nothing; + :meth:`Server.new_session` starts the server when none is running. Each + read of :attr:`Server.sessions`, or of any list below it, runs a tmux + command, so you get the server as it is now and pay a round trip for it. + + Pass ``socket_name`` or ``socket_path`` to choose which server it + addresses. With neither, it addresses your default tmux server; a + ``socket_name`` of your own gives a separate server that leaves that one + alone. + + **What is on here.** Members group by what you reach for them to do. + + **Connecting.** ``config_file`` and ``colors`` pass through to every tmux + command and ``tmux_bin`` names the binary; the socket and these settings + read back as :attr:`Server.socket_name`, :attr:`Server.socket_path`, + :attr:`Server.config_file`, :attr:`Server.colors` and + :attr:`Server.tmux_bin`. :meth:`Server.from_env` addresses the server this + process is already running inside. :meth:`Server.is_alive` answers whether + a server is listening, and :meth:`Server.raise_if_dead` raises when none + is. :meth:`Server.start_server` starts a server without a session, which + tmux ends again at once under its default ``exit-empty`` setting. + + **Finding one thing.** :meth:`Server.has_session` takes a session name and + answers ``True`` or ``False``. For the object itself, look it up in a list + with :meth:`~libtmux._internal.query_list.QueryList.get`, as in + ``server.sessions.get(session_id="$1", default=None)``. Without + ``default``, a miss raises :exc:`~libtmux.exc.ObjectDoesNotExist`. + + **Listing everything.** :attr:`Server.sessions`, :attr:`Server.windows`, + and :attr:`Server.panes` list everything on the server. + :attr:`Server.clients` lists the attached terminals as + :class:`~libtmux.Client` objects, and :attr:`Server.attached_sessions` + lists the sessions that have a client on them. Each is a + :class:`~libtmux._internal.query_list.QueryList`. + :meth:`Server.list_clients` returns tmux's raw lines for the same clients. + + An empty list can mean "nothing there" or "no server". When nothing is + listening, all five lists read as empty, and :attr:`Server.sessions`, + :attr:`Server.clients`, and :attr:`Server.attached_sessions` also read as + empty on any other tmux failure instead of raising. + :meth:`Server.list_clients` raises :exc:`~libtmux.exc.LibTmuxException` + instead. Check :meth:`Server.is_alive`, or call + :meth:`Server.raise_if_dead` to get an exception. + + **Querying things and getting around.** + :meth:`~libtmux._internal.query_list.QueryList.filter` and + :meth:`~libtmux._internal.query_list.QueryList.get` select from any of + those lists by field lookup in Python (see :ref:`querylist-filtering`). + :meth:`Server.search_sessions`, :meth:`Server.search_windows`, and + :meth:`Server.search_panes` hand tmux a filter instead, so the rows it + rejects never become objects (see :ref:`native-filtering`); a malformed + filter matches nothing and reports no error. To move between levels, go + down with :attr:`Session.windows` and :attr:`Window.panes`, up with + :attr:`Pane.window`, :attr:`Pane.session`, and :attr:`Window.session`, and + to whatever has focus with :attr:`Session.active_window`, + :attr:`Session.active_pane`, and :attr:`Window.active_pane` (see + :ref:`traversal`). + + **Changing things.** :meth:`Server.new_session` creates a session. + :meth:`Server.kill_session` ends one; a bare name that matches no session + exactly falls back to a prefix match, so pass ``=name`` to match one + session exactly. :meth:`Server.kill` ends the + server and every session on it, and so does leaving a ``with`` block + around a :class:`Server` (see :ref:`context_managers`). With no socket + named, that server is your default tmux. + + **Options and hooks.** :meth:`Server.show_option`, + :meth:`Server.show_options`, :meth:`Server.set_option`, and + :meth:`Server.unset_option` read and write server options. + :meth:`Server.show_hook`, :meth:`Server.show_hooks`, + :meth:`Server.set_hook`, :meth:`Server.set_hooks`, + :meth:`Server.unset_hook`, and :meth:`Server.run_hook` do the same for + hooks. Both default to the server scope, through + :attr:`Server.default_option_scope` and :attr:`Server.default_hook_scope` + (see :ref:`options-and-hooks`). The server's global environment is + :meth:`Server.show_environment`, :meth:`Server.getenv`, + :meth:`Server.set_environment`, :meth:`Server.unset_environment`, and + :meth:`Server.remove_environment`. + + **Everything else tmux keeps.** Paste buffers are + :meth:`Server.set_buffer`, :meth:`Server.show_buffer`, + :meth:`Server.list_buffers`, :meth:`Server.delete_buffer`, + :meth:`Server.save_buffer`, and :meth:`Server.load_buffer`. Key bindings + are :meth:`Server.bind_key`, :meth:`Server.unbind_key`, and + :meth:`Server.list_keys`. :meth:`Server.list_commands` lists the commands + tmux knows and :meth:`Server.source_file` loads a configuration file. + :meth:`Server.run_shell` and :meth:`Server.if_shell` run shell commands, + :meth:`Server.wait_for` waits on, signals, or locks a channel, and + :meth:`Server.server_access` manages which users may attach. The message + log is :meth:`Server.show_messages`; :meth:`Server.display_message` shows + a message, or with ``get_text=True`` returns the expanded text. The prompt + history is :meth:`Server.show_prompt_history` and + :meth:`Server.clear_prompt_history`. + + **Things that need a terminal.** These need a terminal or other attached + client and raise :exc:`~libtmux.exc.LibTmuxException` without one: + :meth:`Server.attach_session`, :meth:`Server.switch_client`, + :meth:`Server.detach_client`, :meth:`Server.detach_all_clients`, + :meth:`Server.lock_client`, :meth:`Server.suspend_client`, + :meth:`Server.refresh_client`, :meth:`Server.display_menu`, + :meth:`Server.command_prompt`, and :meth:`Server.confirm_before`. + :meth:`Server.display_menu` needs a real terminal; a control-mode client + does not count. :meth:`Server.lock_server` locks every attached client and + does nothing when there are none. See :ref:`clients`. + + Anything tmux can do that is not listed here goes through + :meth:`Server.cmd`. It runs any tmux command against this server and + returns a :class:`~libtmux.common.tmux_cmd` holding ``stdout``, + ``stderr``, and ``returncode``; a tmux error shows up there instead of + being raised. Parameters ---------- @@ -126,6 +236,24 @@ class Server( >>> server.sessions[0].active_pane Pane(%1 Window(@1 1:..., Session($1 ...))) + Look one session up in the list. With ``default=None`` a miss gives + ``None``: + + >>> server.sessions.get(session_id=session.session_id) == session + True + + >>> server.sessions.get(session_id="$99", default=None) is None + True + + An empty list does not say whether a server is listening: + + >>> idle = Server(socket_name="no_exist") + >>> idle.sessions + [] + + >>> idle.is_alive() + False + The server can be used as a context manager to ensure proper cleanup: >>> with Server() as server: @@ -831,7 +959,7 @@ def list_commands(self, *, command_name: str | None = None) -> list[str]: def lock_server(self) -> None: """Lock the tmux server via ``$ tmux lock-server``. - Requires an attached client. + Locks every attached client; with none attached it does nothing. >>> with control_mode() as ctl: ... server.lock_server() From 62f45bf3a4be729261beeec4e5c2e0b8939ea043 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Oct 2026 17:25:44 -0500 Subject: [PATCH 2/4] docs(CHANGES) Server members grouped by purpose why: The unreleased entry did not record that the Server reference now opens with a grouped member overview and states how an empty list reads when tmux is unreachable. what: - Note the Server overview under Documentation --- CHANGES | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES b/CHANGES index 16765e2a1e..9d19507613 100644 --- a/CHANGES +++ b/CHANGES @@ -57,6 +57,17 @@ environment-setup plumbing, so each example leads with the constructor call it demonstrates instead of the socket-path and `$TMUX` boilerplate needed to run it. +#### Server members grouped by purpose (#785) + +The {class}`~libtmux.Server` API reference now opens with what a server is and +a "What is on here" overview that groups every current member by what you reach +for it to do, from connecting and listing to options, hooks, and the calls that +need an attached terminal, each linked to its entry. It also states that +{attr}`~libtmux.Server.sessions`, {attr}`~libtmux.Server.clients`, and +{attr}`~libtmux.Server.attached_sessions` read as empty when tmux cannot be +reached, and points to {meth}`~libtmux.Server.is_alive` for telling that from +an empty server. + ### Development #### CI recovers from incompatible tmux caches (#835) From 530e4a6f9c9c4c46102e948c3ab0fd1a88ee11ae Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 6 Nov 2026 19:49:42 -0600 Subject: [PATCH 3/4] py(deps[dev]) Raise Ruff floor to 0.17.3 why: Keep development and CI linting on the same Ruff release, including corrected f-string conversions and safer lint fixes. what: - Raise Ruff floors in the development and lint dependency groups - Lock Ruff 0.17.3 with its published source and wheel hashes Ruff release notes: https://github.com/astral-sh/ruff/releases/tag/0.17.2 https://github.com/astral-sh/ruff/releases/tag/0.17.3 --- pyproject.toml | 4 ++-- uv.lock | 46 +++++++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8558760667..1d80f80423 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ dev = [ "coverage", "pytest-cov", # Lint - "ruff>=0.17.1", + "ruff>=0.17.3", "mypy", ] @@ -95,7 +95,7 @@ coverage =[ ] lint = [ "typing-extensions; python_version < '3.11'", - "ruff>=0.17.1", + "ruff>=0.17.3", "mypy", ] diff --git a/uv.lock b/uv.lock index 4d9302a7d7..e9dba824de 100644 --- a/uv.lock +++ b/uv.lock @@ -829,7 +829,7 @@ dev = [ { name = "pytest-rerunfailures" }, { name = "pytest-watcher" }, { name = "pytest-xdist" }, - { name = "ruff", specifier = ">=0.17.1" }, + { name = "ruff", specifier = ">=0.17.3" }, { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-api-style", specifier = "==0.1.0a39" }, { name = "sphinx-autodoc-pytest-fixtures", specifier = "==0.1.0a39" }, @@ -844,7 +844,7 @@ docs = [ ] lint = [ { name = "mypy" }, - { name = "ruff", specifier = ">=0.17.1" }, + { name = "ruff", specifier = ">=0.17.3" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] testing = [ @@ -1401,27 +1401,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.17.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e7/e6d5749983b020e6cb1055abec0a6f361a8e94f0184cdcf56d4760995f02/ruff-0.17.1.tar.gz", hash = "sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f", size = 5323349, upload-time = "2026-10-15T15:23:18.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/78/90c46ca6250709f7c37e397b60d06061adee8d0f7d13959ed6a8e49b14fd/ruff-0.17.1-py3-none-linux_armv6l.whl", hash = "sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a", size = 10147528, upload-time = "2026-10-15T15:22:33.667Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d2/a2e28bc35b083ae51fa07dfba5280a667c8b01ecaee7802fc8849dcd5c46/ruff-0.17.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb", size = 10225974, upload-time = "2026-10-15T15:22:36.328Z" }, - { url = "https://files.pythonhosted.org/packages/8e/71/7a9029a81c8206ee01a98f6cfeeb56e6c095af86857581a37045c46d7e94/ruff-0.17.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74", size = 9270361, upload-time = "2026-10-15T15:22:38.651Z" }, - { url = "https://files.pythonhosted.org/packages/c0/8c/e075e9c917cb0575297880c96768304e881240171c870cbd8d9b7b54bec2/ruff-0.17.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978", size = 9938300, upload-time = "2026-10-15T15:22:41.284Z" }, - { url = "https://files.pythonhosted.org/packages/87/27/0b4bb926a49de2383f161272b4d863571a4c7ecdbf595a6bc58813a13367/ruff-0.17.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640", size = 9948464, upload-time = "2026-10-15T15:22:43.681Z" }, - { url = "https://files.pythonhosted.org/packages/14/27/518d57e0e02aecb8ea4acac6b57b51a53173d25fa734952bfdd7d531e88e/ruff-0.17.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1", size = 10638639, upload-time = "2026-10-15T15:22:46.081Z" }, - { url = "https://files.pythonhosted.org/packages/01/82/d89a4e498ade59e6cee52d67f8da569019372212b87468c1fdeae884601c/ruff-0.17.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05", size = 11337238, upload-time = "2026-10-15T15:22:48.894Z" }, - { url = "https://files.pythonhosted.org/packages/a2/7c/3b0fdef9352656317c977086fe1d802902406e4f2a0d8578da90c0e6d1c5/ruff-0.17.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785", size = 10896327, upload-time = "2026-10-15T15:22:51.45Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/5566290219708ce2928f3a29c4f843a1185b341ec6111c4631a33c5ceced/ruff-0.17.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189", size = 10350811, upload-time = "2026-10-15T15:22:54.401Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d2/ca8462dabb3c413990d49703ba306c30c862195a75448d080fa8fcce9775/ruff-0.17.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467", size = 10569994, upload-time = "2026-10-15T15:22:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ad/dd1864c1f1db17c8cfb297ce70b352388e6977620f66f7182c52855407df/ruff-0.17.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab", size = 10059672, upload-time = "2026-10-15T15:23:00.084Z" }, - { url = "https://files.pythonhosted.org/packages/65/45/3f56cf1d2168c2165cdd5ac6f9668aee66a4cfdd4caf2b2cc5ccb2b1d568/ruff-0.17.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca", size = 9947803, upload-time = "2026-10-15T15:23:03.132Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4b/5457cb2be4c1d1ea5e919558131a0f197a4a454a0530d9e30820e8206e4c/ruff-0.17.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4", size = 10351807, upload-time = "2026-10-15T15:23:05.628Z" }, - { url = "https://files.pythonhosted.org/packages/49/fb/db15b4f8fa24a1e36b99e1401f42ef6df16ee299a48670d5d262b771e7d7/ruff-0.17.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a", size = 10765716, upload-time = "2026-10-15T15:23:08.092Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e8/1e186578076aff7a80735f625005fde678412ab609cd323ed182eb7d6b87/ruff-0.17.1-py3-none-win32.whl", hash = "sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a", size = 9895093, upload-time = "2026-10-15T15:23:10.582Z" }, - { url = "https://files.pythonhosted.org/packages/6a/2e/da3d9589bae8e8b58f3a48bd5bc0b980232fb14a83bf02c3be7660f87c1b/ruff-0.17.1-py3-none-win_amd64.whl", hash = "sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7", size = 10097955, upload-time = "2026-10-15T15:23:13.309Z" }, - { url = "https://files.pythonhosted.org/packages/df/c9/0f2a4dea06a5e12c1191ee3d96b1e1233797d0d5e18f97a2b909a95a9836/ruff-0.17.1-py3-none-win_arm64.whl", hash = "sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4", size = 9853542, upload-time = "2026-10-15T15:23:16.05Z" }, +version = "0.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" }, + { url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" }, + { url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" }, + { url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" }, + { url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" }, + { url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" }, ] [[package]] From 7d4ad0bf622fcefcebc5ef3ed9ba3ed428a09ee6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 6 Nov 2026 21:16:01 -0600 Subject: [PATCH 4/4] CHANGES(docs): Record the Ruff development floor why: Contributors need the lint-tool requirement alongside other development environment changes. what: - Document the Ruff 0.17.3 minimum for development and lint checks --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 9d19507613..08b9729e2e 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,10 @@ an empty server. ### Development +#### Ruff 0.17.3 for development (#785) + +Minimum `ruff>=0.17.3` (was `>=0.17.1`) for development and CI lint checks. + #### CI recovers from incompatible tmux caches (#835) CI rebuilds incompatible cached tmux installations before running tests.