diff --git a/AGENTS.md b/AGENTS.md index add4ce1637..ac75e4525f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -518,13 +518,13 @@ Global workspace directories: ← heading() ```python colors = Colors() -colors.heading("Section:") # Cyan + bold (section headers) -colors.highlight("item") # Magenta + bold (primary content) -colors.info("/path/to/file") # Cyan (paths, supplementary info) -colors.muted("label:") # Blue (metadata, labels) -colors.success("ok") # Green (success states) -colors.warning("caution") # Yellow (warnings) -colors.error("failed") # Red (errors) +colors.heading("Section:") # Cyan + bold (section headers) +colors.highlight("item") # Magenta + bold (primary content) +colors.info("/path/to/file") # Cyan (paths, supplementary info) +colors.muted("label:") # Blue (metadata, labels) +colors.success("ok") # Green (success states) +colors.warning("caution") # Yellow (warnings) +colors.error("failed") # Red (errors) ``` ### Key Rules diff --git a/CHANGES b/CHANGES index fb52f90975..802134c18e 100644 --- a/CHANGES +++ b/CHANGES @@ -75,6 +75,22 @@ The workspace, plugin, and CLI types now say what each field holds. They previously reached the rendered API reference as "Alias for field number 0" or as a bare name carrying only its type. +#### Minimum `ruff>=0.16.0` (was unpinned) (#1081) + +Contributors and CI now resolve the same linter release instead of whatever +each machine happened to install. ruff 0.16.0 also formats Python code blocks +inside Markdown, so documentation examples are held to the same style as +source. + +#### ruff's default rule set is enabled (#1081) + +The lint configuration now extends ruff's curated default rule set instead of +replacing it. An explicit `select` overrides those defaults, so the project had +been opting out of every rule outside its own prefix list. The findings this +surfaced are fixed in place, and the idioms it flagged as intentional — `exec` +in the Sphinx config and in `tmuxp shell`, the catch-all in the log formatter — +carry per-file ignores that record why. + ## tmuxp 1.74.0 (2026-07-04) tmuxp 1.74.0 pairs a libtmux upgrade with a documentation overhaul. It bumps libtmux to 0.61.0 — hardening tmux 3.7 patch-line support — and teaches `tmuxp debug-info` to report the exact tmux patch release (`3.7a`/`3.7b`) instead of the numeric-normalized version. The docs also gain theme-aware inline diagrams and a concept-first rewrite that leads with what each feature is before its configuration. diff --git a/docs/_ext/aafig.py b/docs/_ext/aafig.py index 6f90d51378..0ef515f04d 100644 --- a/docs/_ext/aafig.py +++ b/docs/_ext/aafig.py @@ -160,7 +160,7 @@ def render_aafig_images(app: Sphinx, doctree: nodes.Node) -> None: class AafigureNotInstalled(AafigError): def __init__(self, *args: object, **kwargs: object) -> None: - return super().__init__("aafigure module not installed", *args, **kwargs) + super().__init__("aafigure module not installed", *args, **kwargs) def render_aafigure( diff --git a/docs/_ext/tmux_layout.py b/docs/_ext/tmux_layout.py index 1f74fc181a..5f4b9935c6 100644 --- a/docs/_ext/tmux_layout.py +++ b/docs/_ext/tmux_layout.py @@ -246,9 +246,11 @@ def render_layout(panes: list[list[str]], layout: str, size: tuple[int, int]) -> vb_w, vb_h = w * _CW, h * _CH svg_id = f"tmux-layout-{next(_SVG_IDS)}" parts = [ - f'', + ( + f'' + ), ] parts.append("") for index, rect in enumerate(rects): diff --git a/docs/topics/plugins.md b/docs/topics/plugins.md index d664cb633f..5d40d08a87 100644 --- a/docs/topics/plugins.md +++ b/docs/topics/plugins.md @@ -116,21 +116,21 @@ class MyTmuxpPlugin(TmuxpPlugin): # Optional version-dependency configuration. See the Plugin API # docs for every supported parameter. config = { - 'tmuxp_min_version': '1.6.2', + "tmuxp_min_version": "1.6.2", } TmuxpPlugin.__init__( self, - plugin_name='tmuxp-plugin-my-tmuxp-plugin', + plugin_name="tmuxp-plugin-my-tmuxp-plugin", **config, ) def before_workspace_builder(self, session): - session.rename_session('my-new-session-name') + session.rename_session("my-new-session-name") def reattach(self, session): - now = datetime.datetime.now().strftime('%Y-%m-%d') - session.rename_session('session_{}'.format(now)) + now = datetime.datetime.now().strftime("%Y-%m-%d") + session.rename_session("session_{}".format(now)) ``` Once it's installed in the same environment, name it in a workspace file: diff --git a/pyproject.toml b/pyproject.toml index 24dd740b2c..c19d2c5f9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ dev = [ "coverage", "pytest-cov", # Lint - "ruff", + "ruff>=0.16.0", "mypy", "types-docutils", "types-Pygments", @@ -109,7 +109,7 @@ coverage =[ "pytest-cov", ] lint = [ - "ruff", + "ruff>=0.16.0", "mypy", "types-docutils", "types-Pygments", @@ -198,7 +198,10 @@ ignore_missing_imports = true target-version = "py310" [tool.ruff.lint] -select = [ +# `select` is deliberately unset: ruff 0.16 enables a curated default rule +# set, and an explicit `select` would replace it rather than extend it. +# `extend-select` layers this project's additional linters on top. +extend-select = [ "E", # pycodestyle "F", # pyflakes "I", # isort @@ -215,7 +218,7 @@ select = [ "PERF", # Perflint "RUF", # Ruff-specific rules "D", # pydocstyle - "FA100", # future annotations + "FA100", # future annotations ] ignore = [ "COM812", # missing trailing comma, ruff format conflict @@ -248,6 +251,26 @@ convention = "numpy" "src/tmuxp/workspace/finders.py" = ["PTH"] "src/tmuxp/cli/*.py" = ["PTH"] "docs/_ext/aafig.py" = ["PTH"] +# `breakpoint = breakpoint` is not a no-op at module scope: the right-hand +# side resolves to the builtin, and the assignment binds it as a module +# attribute so `from tmuxp._compat import breakpoint` resolves. Module +# attribute lookup does not fall back to builtins, so dropping the line +# breaks the import in `tmuxp.cli.shell`. +"src/tmuxp/_compat.py" = ["PLW0127"] +# Sphinx reads the package's version metadata by `exec`-ing `__about__.py` +# into a dict, so `conf.py` never imports the package it documents. +"docs/conf.py" = ["S102"] +# `tmuxp shell -c` documents itself as "execute python code in libtmux and +# exit"; running the operator's own code in a tmux-aware namespace is the +# command's purpose, not an injection sink. +"src/tmuxp/cli/shell.py" = ["S102"] +# The interactive console honors `$PYTHONSTARTUP` and `~/.pythonrc.py` by +# `exec`-ing them, mirroring how CPython's own REPL sources them. +"src/tmuxp/shell.py" = ["S102"] +# A log formatter must never raise: `record.getMessage()` interpolates +# caller-supplied args, so anything their `__str__` throws has to be +# rendered into the line instead of propagating into the caller. +"src/tmuxp/log.py" = ["BLE001"] [tool.pytest.ini_options] addopts = "--reruns=0 --tb=short --no-header --showlocals --doctest-modules" diff --git a/src/tmuxp/_compat.py b/src/tmuxp/_compat.py index 51a055f7f5..6efe60139a 100644 --- a/src/tmuxp/_compat.py +++ b/src/tmuxp/_compat.py @@ -5,7 +5,7 @@ logger = logging.getLogger(__name__) -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 PYMINOR = sys.version_info[1] PYPATCH = sys.version_info[2] @@ -30,12 +30,7 @@ def _identity(x: object) -> object: return x -if PY3 and PYMINOR >= 7: - breakpoint = breakpoint # noqa: A001 -else: - import pdb - - breakpoint = pdb.set_trace # noqa: A001 +breakpoint = breakpoint # noqa: A001 implements_to_string = _identity diff --git a/src/tmuxp/_internal/colors.py b/src/tmuxp/_internal/colors.py index a9b350016f..8826481cc6 100644 --- a/src/tmuxp/_internal/colors.py +++ b/src/tmuxp/_internal/colors.py @@ -729,7 +729,7 @@ class UnknownStyleColor(Exception): """ def __init__(self, color: CLIColour, *args: object, **kwargs: object) -> None: - return super().__init__(f"Unknown color {color!r}", *args, **kwargs) + super().__init__(f"Unknown color {color!r}", *args, **kwargs) def style( diff --git a/src/tmuxp/_internal/private_path.py b/src/tmuxp/_internal/private_path.py index 8fa0cec972..c3e735ca2a 100644 --- a/src/tmuxp/_internal/private_path.py +++ b/src/tmuxp/_internal/private_path.py @@ -14,6 +14,8 @@ logger = logging.getLogger(__name__) if t.TYPE_CHECKING: + from typing_extensions import Self + PrivatePathBase = pathlib.Path else: PrivatePathBase = type(pathlib.Path()) @@ -45,7 +47,7 @@ class PrivatePath(PrivatePathBase): 'config: ~/.tmuxp/config.yaml' """ - def __new__(cls, *args: t.Any, **kwargs: t.Any) -> PrivatePath: + def __new__(cls, *args: t.Any, **kwargs: t.Any) -> Self: """Create a new PrivatePath instance.""" return super().__new__(cls, *args, **kwargs) diff --git a/src/tmuxp/cli/_progress.py b/src/tmuxp/cli/_progress.py index b2783f3ff7..b1cfb1cad6 100644 --- a/src/tmuxp/cli/_progress.py +++ b/src/tmuxp/cli/_progress.py @@ -25,6 +25,8 @@ if t.TYPE_CHECKING: import types + from typing_extensions import Self + # ANSI Escape Sequences HIDE_CURSOR = "\033[?25l" @@ -1117,7 +1119,7 @@ def success(self, text: str | None = None) -> None: self.stream.write(f"{msg}\n") self.stream.flush() - def __enter__(self) -> Spinner: + def __enter__(self) -> Self: self.start() return self diff --git a/src/tmuxp/cli/convert.py b/src/tmuxp/cli/convert.py index a92cfebbfa..fb62df063a 100644 --- a/src/tmuxp/cli/convert.py +++ b/src/tmuxp/cli/convert.py @@ -72,7 +72,7 @@ class ConvertUnknownFileType(exc.TmuxpException): """Raise if tmuxp convert encounters an unknown filetype.""" def __init__(self, ext: str, *args: object, **kwargs: object) -> None: - return super().__init__( + super().__init__( f"Unknown filetype: {ext} (valid: [.json, .yaml, .yml])", ) diff --git a/src/tmuxp/exc.py b/src/tmuxp/exc.py index 5168aa10d0..4219feecc6 100644 --- a/src/tmuxp/exc.py +++ b/src/tmuxp/exc.py @@ -31,7 +31,7 @@ def __init__( msg = "Session not found" if session_target is not None: msg += f": {session_target}" - return super().__init__(msg, *args, **kwargs) + super().__init__(msg, *args, **kwargs) class WindowNotFound(TmuxpException): @@ -46,7 +46,7 @@ def __init__( msg = "Window not found" if window_target is not None: msg += f": {window_target}" - return super().__init__(msg, *args, **kwargs) + super().__init__(msg, *args, **kwargs) class PaneNotFound(TmuxpException): @@ -61,7 +61,7 @@ def __init__( msg = "Pane not found" if pane_target is not None: msg += f": {pane_target}" - return super().__init__(msg, *args, **kwargs) + super().__init__(msg, *args, **kwargs) class EmptyWorkspaceException(WorkspaceError): @@ -72,14 +72,14 @@ def __init__( *args: object, **kwargs: object, ) -> None: - return super().__init__("Session configuration is empty.", *args, **kwargs) + super().__init__("Session configuration is empty.", *args, **kwargs) class SessionMissingWorkspaceException(WorkspaceError, ObjectDoesNotExist): """Session missing while loading tmuxp workspace.""" def __init__(self, *args: object, **kwargs: object) -> None: - return super().__init__( + super().__init__( "No session object exists for WorkspaceBuilder. " "Tip: Add session_name in constructor or run WorkspaceBuilder.build()", *args, @@ -91,7 +91,7 @@ class ActiveSessionMissingWorkspaceException(WorkspaceError): """Active session cannot be found while loading tmuxp workspace.""" def __init__(self, *args: object, **kwargs: object) -> None: - return super().__init__("No session active.", *args, **kwargs) + super().__init__("No session active.", *args, **kwargs) class WorkspaceBuilderError(WorkspaceError): @@ -121,7 +121,7 @@ def __init__( " Provide a Python dotted path (e.g. 'package.module:Builder') " "or register an entry point in the 'tmuxp.workspace_builders' group." ) - return super().__init__(msg, *args, **kwargs) + super().__init__(msg, *args, **kwargs) class WorkspaceBuilderImportError(WorkspaceBuilderError): @@ -148,7 +148,7 @@ def __init__( " Confirm the builder is importable, or add its directory to " "'workspace_builder_paths'." ) - return super().__init__(msg, *args, **kwargs) + super().__init__(msg, *args, **kwargs) class InvalidWorkspaceBuilder(WorkspaceBuilderError): @@ -167,7 +167,7 @@ def __init__( ) -> None: msg = f"{target!r} is not a valid workspace builder" msg += f": {reason}" if reason else "." - return super().__init__(msg, *args, **kwargs) + super().__init__(msg, *args, **kwargs) class WorkspaceBuilderPathError(WorkspaceBuilderError): @@ -186,7 +186,7 @@ def __init__( ) -> None: msg = f"workspace_builder_paths entry is invalid: {path}." msg += f" {reason}" if reason else " Each entry must be an existing directory." - return super().__init__(msg, *args, **kwargs) + super().__init__(msg, *args, **kwargs) class InvalidWorkspaceBuilderOption(WorkspaceBuilderError): @@ -197,7 +197,7 @@ class InvalidWorkspaceBuilderOption(WorkspaceBuilderError): """ def __init__(self, reason: str, *args: object, **kwargs: object) -> None: - return super().__init__( + super().__init__( f"Invalid workspace_builder_options: {reason}", *args, **kwargs, diff --git a/src/tmuxp/log.py b/src/tmuxp/log.py index d4867f052a..19fb9a67aa 100644 --- a/src/tmuxp/log.py +++ b/src/tmuxp/log.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """Log utilities for tmuxp.""" from __future__ import annotations diff --git a/src/tmuxp/workspace/builder/classic.py b/src/tmuxp/workspace/builder/classic.py index c465eb29e4..12fef7e09c 100644 --- a/src/tmuxp/workspace/builder/classic.py +++ b/src/tmuxp/workspace/builder/classic.py @@ -95,16 +95,21 @@ def _wait_for_pane_ready( def get_default_columns() -> int: """Return default session column size use when building new tmux sessions.""" return int( - os.getenv("TMUXP_DEFAULT_COLUMNS", os.getenv("COLUMNS", COLUMNS_FALLBACK)), + os.getenv( + "TMUXP_DEFAULT_COLUMNS", + os.getenv("COLUMNS", str(COLUMNS_FALLBACK)), + ), ) -ROWS_FALLBACK = int(os.getenv("TMUXP_DEFAULT_ROWS", os.getenv("ROWS", 24))) +ROWS_FALLBACK = int(os.getenv("TMUXP_DEFAULT_ROWS", os.getenv("ROWS", "24"))) def get_default_rows() -> int: """Return default session row size use when building new tmux sessions.""" - return int(os.getenv("TMUXP_DEFAULT_ROWS", os.getenv("ROWS", ROWS_FALLBACK))) + return int( + os.getenv("TMUXP_DEFAULT_ROWS", os.getenv("ROWS", str(ROWS_FALLBACK))), + ) class ClassicWorkspaceBuilder: @@ -586,7 +591,6 @@ def build(self, session: Session | None = None, append: bool = False) -> None: focus_pane = None for pane, pane_config in self.iter_create_panes(window, window_config): assert isinstance(pane, Pane) - pane = pane if pane_config.get("focus"): focus_pane = pane diff --git a/src/tmuxp/workspace/loader.py b/src/tmuxp/workspace/loader.py index b022a4da21..fa6aa654d9 100644 --- a/src/tmuxp/workspace/loader.py +++ b/src/tmuxp/workspace/loader.py @@ -228,7 +228,6 @@ def trickle(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: for window_dict in workspace_dict["windows"]: # Prepend start_directory to relative window commands if session_start_directory: - session_start_directory = session_start_directory if "start_directory" not in window_dict: window_dict["start_directory"] = session_start_directory elif not any( diff --git a/src/tmuxp/workspace/validation.py b/src/tmuxp/workspace/validation.py index e7fe4da743..e9de3b4a37 100644 --- a/src/tmuxp/workspace/validation.py +++ b/src/tmuxp/workspace/validation.py @@ -18,7 +18,7 @@ class SessionNameMissingValidationError(SchemaValidationError): """Tmuxp configuration error for session name missing.""" def __init__(self, *args: object, **kwargs: object) -> None: - return super().__init__( + super().__init__( 'workspace requires "session_name"', *args, **kwargs, @@ -29,7 +29,7 @@ class WindowListMissingValidationError(SchemaValidationError): """Tmuxp configuration error for window list missing.""" def __init__(self, *args: object, **kwargs: object) -> None: - return super().__init__( + super().__init__( 'workspace requires list of "windows"', *args, **kwargs, @@ -40,7 +40,7 @@ class WindowNameMissingValidationError(SchemaValidationError): """Tmuxp configuration error for missing window_name.""" def __init__(self, *args: object, **kwargs: object) -> None: - return super().__init__( + super().__init__( 'workspace window is missing "window_name"', *args, **kwargs, @@ -51,7 +51,7 @@ class InvalidPluginsValidationError(SchemaValidationError): """Tmuxp configuration error for invalid plugins.""" def __init__(self, plugins: t.Any, *args: object, **kwargs: object) -> None: - return super().__init__( + super().__init__( '"plugins" only supports list type. ' f" Received {type(plugins)}, " f"value: {plugins}", diff --git a/tests/cli/test_help_examples.py b/tests/cli/test_help_examples.py index 9cbe365db2..5649fd858a 100644 --- a/tests/cli/test_help_examples.py +++ b/tests/cli/test_help_examples.py @@ -245,6 +245,7 @@ def test_search_no_args_shows_help() -> None: ["tmuxp", "search"], capture_output=True, text=True, + check=False, ) # Should show help (usage line present) assert "usage: tmuxp search" in result.stdout diff --git a/tests/test_util.py b/tests/test_util.py index 098c8c212b..caa8cac513 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -17,8 +17,6 @@ from .constants import FIXTURE_PATH if t.TYPE_CHECKING: - import pathlib - from libtmux.server import Server diff --git a/tests/tests/test_helpers.py b/tests/tests/test_helpers.py index aa04cee8c0..8137ea87ac 100644 --- a/tests/tests/test_helpers.py +++ b/tests/tests/test_helpers.py @@ -14,7 +14,6 @@ def test_temp_session_kills_session_on_exit(server: Server) -> None: """Test temp_session() context manager kills session on exit.""" - server = server session_name = get_test_session_name(server=server) with temp_session(server=server, session_name=session_name): @@ -27,7 +26,6 @@ def test_temp_session_kills_session_on_exit(server: Server) -> None: @pytest.mark.flaky(reruns=5) def test_temp_session_if_session_killed_before_exit(server: Server) -> None: """Handles situation where session already closed within context.""" - server = server session_name = get_test_session_name(server=server) with temp_session(server=server, session_name=session_name): diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 32cbdffcf4..baf588a483 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -51,9 +51,8 @@ def test_split_windows(session: Session) -> None: window_count = len(session.windows) # current window count assert len(session.windows) == window_count for w, wconf in builder.iter_create_windows(session): - for p in builder.iter_create_panes(w, wconf): + for _p in builder.iter_create_panes(w, wconf): w.select_layout("tiled") # fix glitch with pane size - p = p assert len(session.windows) == window_count assert isinstance(w, Window) @@ -72,9 +71,8 @@ def test_split_windows_three_pane(session: Session) -> None: window_count = len(session.windows) # current window count assert len(session.windows) == window_count for w, wconf in builder.iter_create_windows(session): - for p in builder.iter_create_panes(w, wconf): + for _p in builder.iter_create_panes(w, wconf): w.select_layout("tiled") # fix glitch with pane size - p = p assert len(session.windows) == window_count assert isinstance(w, Window) @@ -304,9 +302,8 @@ def test_window_options( window_count = len(session.windows) # current window count assert len(session.windows) == window_count for w, wconf in builder.iter_create_windows(session): - for p in builder.iter_create_panes(w, wconf): + for _p in builder.iter_create_panes(w, wconf): w.select_layout("tiled") # fix glitch with pane size - p = p assert len(session.windows) == window_count assert isinstance(w, Window) assert w.show_option("main-pane-height") == 5 diff --git a/tests/workspace/test_freezer.py b/tests/workspace/test_freezer.py index d42386ecef..653db9a08c 100644 --- a/tests/workspace/test_freezer.py +++ b/tests/workspace/test_freezer.py @@ -33,7 +33,6 @@ def test_freeze_config(session: Session) -> None: time.sleep(0.50) - session = session new_config = freezer.freeze(session) validation.validate_schema(new_config) diff --git a/uv.lock b/uv.lock index 37e38b4904..8a3a6fabf3 100644 --- a/uv.lock +++ b/uv.lock @@ -1160,27 +1160,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, - { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]] @@ -1709,7 +1709,7 @@ dev = [ { name = "pytest-mock" }, { name = "pytest-rerunfailures" }, { name = "pytest-watcher" }, - { name = "ruff" }, + { name = "ruff", specifier = ">=0.16.0" }, { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-api-style", specifier = "==0.0.1a36" }, { name = "sphinx-autodoc-argparse", specifier = "==0.0.1a36" }, @@ -1732,7 +1732,7 @@ docs = [ ] lint = [ { name = "mypy" }, - { name = "ruff" }, + { name = "ruff", specifier = ">=0.16.0" }, { name = "types-docutils" }, { name = "types-pygments" }, { name = "types-pyyaml" },