From 0aebe47b4982a0b79013ec8e3d58a2b133e74821 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 1 Jul 2023 17:47:20 -0500 Subject: [PATCH 01/13] ci(ruff): Strengthen code quality linting --- pyproject.toml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index eae982a34..f7624d730 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,6 +129,33 @@ exclude_lines = [ "@overload( |$)", ] +[tool.ruff] +target-version = "py37" +select = [ + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "Q", # flake8-quotes + "PTH", # flake8-use-pathlib + "ERA", # eradicate + "SIM", # flake8-simplify + "TRY", # Trycertatops + "PERF", # Perflint + "RUF" # Ruff-specific rules +] + +[tool.ruff.isort] +known-first-party = [ + "libtmux" +] +combine-as-imports = true + +[tool.ruff.per-file-ignores] +"*/__init__.py" = ["F401"] + [build-system] requires = ["poetry_core>=1.0.0"] build-backend = "poetry.core.masonry.api" From 906c038b47f5391dcfc8b1cc0280d5c39d7c44ed Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 05:43:16 -0500 Subject: [PATCH 02/13] refactor(version): Paremetrize InvalidVersion src/libtmux/_vendor/version.py:198:19: TRY003 Avoid specifying long messages outside the exception class" --- src/libtmux/_vendor/version.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libtmux/_vendor/version.py b/src/libtmux/_vendor/version.py index 3cd40b965..0aac18ff4 100644 --- a/src/libtmux/_vendor/version.py +++ b/src/libtmux/_vendor/version.py @@ -62,6 +62,9 @@ class InvalidVersion(ValueError): libtmux._vendor.version.InvalidVersion: Invalid version: 'invalid' """ + def __init__(self, version: str, *args: object): + return super().__init__(f"Invalid version: '{version}'") + class _BaseVersion: _key: CmpKey @@ -195,7 +198,7 @@ def __init__(self, version: str) -> None: # Validate the version and parse it into pieces match = self._regex.search(version) if not match: - raise InvalidVersion(f"Invalid version: '{version}'") + raise InvalidVersion(version=version) # Store the parsed out pieces of the version self._version = _Version( From d35d80b45286a6c480e94b70c7c262e7a550e027 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 1 Jul 2023 17:49:34 -0500 Subject: [PATCH 03/13] ci(ruff): Automated lint fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed 81 errors: - conftest.py: 1 × I001 (unsorted-imports) - docs/conf.py: 2 × RUF100 (unused-noqa) 1 × SIM108 (if-else-block-instead-of-if-exp) 1 × I001 (unsorted-imports) - src/libtmux/_internal/query_list.py: 6 × ERA001 (commented-out-code) 1 × SIM118 (in-dict-keys) - src/libtmux/common.py: 1 × UP031 (printf-string-formatting) 1 × C408 (unnecessary-collection-call) - src/libtmux/formats.py: 2 × ERA001 (commented-out-code) - src/libtmux/neo.py: 3 × ERA001 (commented-out-code) 1 × C408 (unnecessary-collection-call) - src/libtmux/pane.py: 4 × ERA001 (commented-out-code) 4 × RUF002 (ambiguous-unicode-character-docstring) 1 × RUF005 (collection-literal-concatenation) 1 × UP032 (f-string) - src/libtmux/pytest_plugin.py: 1 × F841 (unused-variable) 1 × SIM300 (yoda-conditions) 1 × TRY201 (verbose-raise) 1 × ERA001 (commented-out-code) 1 × SIM105 (suppressible-exception) 1 × I001 (unsorted-imports) 1 × SIM118 (in-dict-keys) 1 × PERF102 (incorrect-dict-iterator) - src/libtmux/server.py: 2 × C408 (unnecessary-collection-call) 1 × RUF005 (collection-literal-concatenation) 1 × B009 (get-attr-with-constant) 1 × ERA001 (commented-out-code) - src/libtmux/session.py: 5 × C408 (unnecessary-collection-call) 2 × RUF015 (unnecessary-iterable-allocation-for-first-element) 1 × UP031 (printf-string-formatting) 1 × ERA001 (commented-out-code) 1 × UP032 (f-string) - src/libtmux/test.py: 1 × C408 (unnecessary-collection-call) - src/libtmux/window.py: 6 × UP032 (f-string) 4 × ERA001 (commented-out-code) 3 × C408 (unnecessary-collection-call) 2 × UP031 (printf-string-formatting) 1 × RUF005 (collection-literal-concatenation) 1 × RUF015 (unnecessary-iterable-allocation-for-first-element) - tests/legacy_api/test_common.py: 1 × SIM300 (yoda-conditions) - tests/legacy_api/test_server.py: 3 × SIM300 (yoda-conditions) - tests/test_common.py: 1 × SIM300 (yoda-conditions) - tests/test_pytest_plugin.py: 1 × ERA001 (commented-out-code) 1 × RUF015 (unnecessary-iterable-allocation-for-first-element) - tests/test_server.py: 3 × SIM300 (yoda-conditions) --- conftest.py | 1 - docs/conf.py | 16 +++++----------- src/libtmux/_internal/query_list.py | 8 +------- src/libtmux/common.py | 9 +++++---- src/libtmux/formats.py | 2 -- src/libtmux/neo.py | 11 +++++------ src/libtmux/pane.py | 12 ++++-------- src/libtmux/pytest_plugin.py | 14 ++++++-------- src/libtmux/server.py | 12 ++++-------- src/libtmux/session.py | 18 ++++++++---------- src/libtmux/test.py | 2 +- src/libtmux/window.py | 27 +++++++++++---------------- tests/legacy_api/test_common.py | 2 +- tests/legacy_api/test_server.py | 6 +++--- tests/test_common.py | 2 +- tests/test_pytest_plugin.py | 3 +-- tests/test_server.py | 6 +++--- 17 files changed, 59 insertions(+), 92 deletions(-) diff --git a/conftest.py b/conftest.py index d45b5ee5b..299041497 100644 --- a/conftest.py +++ b/conftest.py @@ -12,7 +12,6 @@ import typing as t import pytest - from _pytest.doctest import DoctestItem from libtmux.pytest_plugin import USING_ZSH diff --git a/docs/conf.py b/docs/conf.py index 522cdcbc5..fb4f6517b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,13 +1,12 @@ # flake8: NOQA: E501 import contextlib import inspect -import sys -from os.path import relpath import pathlib +import sys import typing as t +from os.path import relpath -import libtmux # NOQA -from libtmux import test # NOQA +import libtmux if t.TYPE_CHECKING: from sphinx.application import Sphinx @@ -166,9 +165,7 @@ } -def linkcode_resolve( - domain: str, info: t.Dict[str, str] -) -> t.Union[None, str]: # NOQA: C901 +def linkcode_resolve(domain: str, info: t.Dict[str, str]) -> t.Union[None, str]: """ Determine the URL corresponding to Python object @@ -216,10 +213,7 @@ def linkcode_resolve( except Exception: lineno = None - if lineno: - linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) - else: - linespec = "" + linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) if lineno else "" fn = relpath(fn, start=pathlib.Path(libtmux.__file__).parent) diff --git a/src/libtmux/_internal/query_list.py b/src/libtmux/_internal/query_list.py index 7fa3d4170..487ca8deb 100644 --- a/src/libtmux/_internal/query_list.py +++ b/src/libtmux/_internal/query_list.py @@ -123,7 +123,7 @@ def lookup_icontains( if isinstance(data, str): return rhs.lower() in data.lower() if isinstance(data, Mapping): - return rhs.lower() in [k.lower() for k in data.keys()] + return rhs.lower() in [k.lower() for k in data] return False @@ -183,7 +183,6 @@ def lookup_in( return rhs in data # TODO: Add a deep Mappingionary matcher # if isinstance(rhs, Mapping) and isinstance(data, Mapping): - # return rhs.items() not in data.items() except Exception: return False return False @@ -205,7 +204,6 @@ def lookup_nin( return rhs not in data # TODO: Add a deep Mappingionary matcher # if isinstance(rhs, Mapping) and isinstance(data, Mapping): - # return rhs.items() not in data.items() except Exception: return False return False @@ -293,10 +291,6 @@ def __eq__( self, other: object, # other: Union[ - # "QueryList[T]", - # List[Mapping[str, str]], - # List[Mapping[str, int]], - # List[Mapping[str, Union[str, Mapping[str, Union[List[str], str]]]]], # ], ) -> bool: data = other diff --git a/src/libtmux/common.py b/src/libtmux/common.py index eac5179bc..5e7e25c42 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -172,7 +172,7 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]: str Value of environment variable """ - tmux_args: t.Tuple[t.Union[str, int], ...] = tuple() + tmux_args: t.Tuple[t.Union[str, int], ...] = () tmux_args += ("show-environment",) if self._add_option: @@ -425,9 +425,10 @@ def has_minimum_version(raises: bool = True) -> bool: if get_version() < LooseVersion(TMUX_MIN_VERSION): if raises: raise exc.VersionTooLow( - "libtmux only supports tmux %s and greater. This system" - " has %s installed. Upgrade your tmux to use libtmux." - % (TMUX_MIN_VERSION, get_version()) + "libtmux only supports tmux {} and greater. This system" + " has {} installed. Upgrade your tmux to use libtmux.".format( + TMUX_MIN_VERSION, get_version() + ) ) else: return False diff --git a/src/libtmux/formats.py b/src/libtmux/formats.py index 6f36799f1..eff1d7fed 100644 --- a/src/libtmux/formats.py +++ b/src/libtmux/formats.py @@ -41,14 +41,12 @@ ] WINDOW_FORMATS = [ - # format_window() "window_id", "window_name", "window_width", "window_height", "window_layout", "window_panes", - # format_winlink() "window_index", "window_flags", "window_active", diff --git a/src/libtmux/neo.py b/src/libtmux/neo.py index 70267b07d..839345cac 100644 --- a/src/libtmux/neo.py +++ b/src/libtmux/neo.py @@ -40,16 +40,13 @@ class Obj: alternate_saved_x: t.Union[str, None] = None alternate_saved_y: t.Union[str, None] = None # See QUIRK_TMUX_3_1_X_0001 - # buffer_created: t.Union[str, None] = None buffer_name: t.Union[str, None] = None buffer_sample: t.Union[str, None] = None buffer_size: t.Union[str, None] = None # See QUIRK_TMUX_3_1_X_0001 - # client_activity: t.Union[str, None] = None client_cell_height: t.Union[str, None] = None client_cell_width: t.Union[str, None] = None # See QUIRK_TMUX_3_1_X_0001 - # client_created: t.Union[str, None] = None client_discarded: t.Union[str, None] = None client_flags: t.Union[str, None] = None client_height: t.Union[str, None] = None @@ -182,11 +179,13 @@ def _refresh( def fetch_objs( - server: "Server", list_cmd: "ListCmd", list_extra_args: "ListExtraArgs" = None + server: "Server", + list_cmd: "ListCmd", + list_extra_args: "t.Optional[ListExtraArgs]" = None, ) -> OutputsRaw: formats = list(Obj.__dataclass_fields__.keys()) - cmd_args: t.List[t.Union[str, int]] = list() + cmd_args: t.List[t.Union[str, int]] = [] if server.socket_name: cmd_args.insert(0, f"-L{server.socket_name}") @@ -229,7 +228,7 @@ def fetch_obj( obj_key: str, obj_id: str, list_cmd: "ListCmd" = "list-panes", - list_extra_args: "ListExtraArgs" = None, + list_extra_args: "t.Optional[ListExtraArgs]" = None, ) -> OutputRaw: obj_formatters_filtered = fetch_objs( server=server, list_cmd=list_cmd, list_extra_args=list_extra_args diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index cd876c04e..9d62ac1b6 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -103,7 +103,6 @@ def session(self) -> "Session": return self.window.session # - # Command (pane-scoped) # def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: """Return :meth:`Server.cmd` defaulting to ``target_pane`` as target. @@ -114,12 +113,11 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: ``args`` will override using the object's ``pane_id`` as target. """ if not any(arg.startswith("-t") for arg in args): - args = ("-t", self.pane_id) + args + args = ("-t", self.pane_id, *args) return self.server.cmd(cmd, *args, **kwargs) # - # Commands (tmux-like) # def resize_pane(self, *args: t.Any, **kwargs: t.Any) -> "Pane": """ @@ -174,14 +172,14 @@ def capture_pane( Zero is the first line of the visible pane. Positive numbers are lines in the visible pane. Negative numbers are lines in the history. - ‘-’ is the start of the history. + `-` is the start of the history. Default: None end: [str,int] Specify the ending line number. Zero is the first line of the visible pane. Positive numbers are lines in the visible pane. Negative numbers are lines in the history. - ‘-’ is the end of the visible pane + `-` is the end of the visible pane Default: None """ cmd = ["capture-pane", "-p"] @@ -277,7 +275,6 @@ def display_message( return None # - # Commands ("climber"-helpers) # # These are commands that climb to the parent scope's methods with # additional scoped window info. @@ -326,7 +323,6 @@ def split_window( ) # - # Commands (helpers) # def set_width(self, width: int) -> "Pane": """ @@ -380,7 +376,7 @@ def __eq__(self, other: object) -> bool: return self.pane_id == other.pane_id def __repr__(self) -> str: - return "{}({} {})".format(self.__class__.__name__, self.pane_id, self.window) + return f"{self.__class__.__name__}({self.pane_id} {self.window})" # # Aliases diff --git a/src/libtmux/pytest_plugin.py b/src/libtmux/pytest_plugin.py index f24bfdbd0..0a33fd8e8 100644 --- a/src/libtmux/pytest_plugin.py +++ b/src/libtmux/pytest_plugin.py @@ -1,3 +1,4 @@ +import contextlib import getpass import logging import os @@ -80,7 +81,7 @@ def clear_env(monkeypatch: pytest.MonkeyPatch) -> None: tmux show-environment tests were being interrupted due to a lot of crazy env vars. """ - for k, v in os.environ.items(): + for k in os.environ: if not any( needle in k.lower() for needle in [ @@ -226,8 +227,8 @@ def session( try: session = server.new_session(session_name=TEST_SESSION_NAME, **session_params) - except exc.LibTmuxException as e: - raise e + except exc.LibTmuxException: + raise """ Make sure that tmuxp can :ref:`test_builder_visually` and switches to @@ -236,16 +237,13 @@ def session( session_id = session.session_id assert session_id is not None - try: + with contextlib.suppress(exc.LibTmuxException): server.switch_client(target_session=session_id) - except exc.LibTmuxException: - # server.attach_session(session.get('session_id')) - pass for old_test_session in old_test_sessions: logger.debug(f"Old test test session {old_test_session} found. Killing it.") server.kill_session(old_test_session) - assert TEST_SESSION_NAME == session.session_name + assert session.session_name == TEST_SESSION_NAME assert TEST_SESSION_NAME != "tmuxp" return session diff --git a/src/libtmux/server.py b/src/libtmux/server.py index a281d202b..87c08fe35 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -169,7 +169,7 @@ def raise_if_dead(self) -> None: if self.config_file: cmd_args.insert(0, f"-f{self.config_file}") - subprocess.check_call([tmux_bin] + cmd_args) + subprocess.check_call([tmux_bin, *cmd_args]) # # Command @@ -227,7 +227,7 @@ def attached_sessions(self) -> t.List[Session]: """ try: sessions = self.sessions - attached_sessions = list() + attached_sessions = [] for session in sessions: attached = session.session_attached @@ -239,7 +239,6 @@ def attached_sessions(self) -> t.List[Session]: continue return attached_sessions - # return [Session(**s) for s in attached_sessions] or None except Exception: return [] @@ -339,7 +338,7 @@ def attach_session(self, target_session: t.Optional[str] = None) -> None: """ session_check_name(target_session) - tmux_args: t.Tuple[str, ...] = tuple() + tmux_args: t.Tuple[str, ...] = () if target_session: tmux_args += ("-t%s" % target_session,) @@ -572,10 +571,7 @@ def __repr__(self) -> str: f"(socket_name={getattr(self, 'socket_name', 'default')})" ) elif self.socket_path is not None: - return ( - f"{self.__class__.__name__}" - f"(socket_path={getattr(self, 'socket_path')})" - ) + return f"{self.__class__.__name__}" f"(socket_path={self.socket_path})" return f"{self.__class__.__name__}" f"(socket_path=/tmp/tmux-1000/default)" # diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 1793a687f..52b34a4cf 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -151,7 +151,7 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: # if -t is not set in any arg yet if not any("-t" in str(x) for x in args): # insert -t immediately after 1st arg, as per tmux format - new_args: t.Tuple[str, ...] = tuple() + new_args: t.Tuple[str, ...] = () new_args += (args[0],) assert isinstance(self.session_id, str) new_args += ( @@ -163,7 +163,6 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: return self.server.cmd(*args, **kwargs) # - # Commands (tmux-like) # def set_option( self, option: str, value: t.Union[str, int], _global: bool = False @@ -197,7 +196,7 @@ def set_option( elif isinstance(value, bool) and not value: value = "off" - tmux_args: t.Tuple[t.Union[str, int], ...] = tuple() + tmux_args: t.Tuple[t.Union[str, int], ...] = () if _global: tmux_args += ("-g",) @@ -240,7 +239,7 @@ def show_options( Uses ``_global`` for keyword name instead of ``global`` to avoid colliding with reserved keyword. """ - tmux_args: t.Tuple[str, ...] = tuple() + tmux_args: t.Tuple[str, ...] = () if _global: tmux_args += ("-g",) @@ -288,7 +287,7 @@ def show_option( Test and return True/False for on/off string. """ - tmux_args: t.Tuple[str, ...] = tuple() + tmux_args: t.Tuple[str, ...] = () if _global: tmux_args += ("-g",) @@ -303,7 +302,7 @@ def show_option( if not len(cmd.stdout): return None - value_raw: t.List[str] = [item.split(" ") for item in cmd.stdout][0] + value_raw: t.List[str] = next(item.split(" ") for item in cmd.stdout) assert isinstance(value_raw[0], str) assert isinstance(value_raw[1], str) @@ -362,7 +361,7 @@ def attached_window(self) -> "Window": active_windows.append(window) if len(active_windows) == 1: - return list(active_windows)[0] + return next(iter(active_windows)) elif len(active_windows) == 0: raise exc.LibTmuxException("no active windows found") else: @@ -476,7 +475,7 @@ def new_window( ------- :class:`Window` """ - window_args: t.Tuple[str, ...] = tuple() + window_args: t.Tuple[str, ...] = () if not attach: window_args += ("-d",) @@ -494,8 +493,7 @@ def new_window( window_args += ( # empty string for window_index will use the first one available - "-t%s:%s" - % (self.session_id, window_index), + f"-t{self.session_id}:{window_index}", ) if environment: diff --git a/src/libtmux/test.py b/src/libtmux/test.py index 861ac6dda..c1c996218 100644 --- a/src/libtmux/test.py +++ b/src/libtmux/test.py @@ -305,7 +305,7 @@ class EnvironmentVarGuard: def __init__(self) -> None: self._environ = os.environ self._unset: t.Set[str] = set() - self._reset: t.Dict[str, str] = dict() + self._reset: t.Dict[str, str] = {} def set(self, envvar: str, value: str) -> None: if envvar not in self._environ: diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 072707a3d..6ffbfe2fa 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -125,7 +125,6 @@ def panes(self) -> QueryList["Pane"]: # type: ignore return QueryList(panes) # - # Command (pane-scoped) # def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: """Return :meth:`Server.cmd` defaulting to ``target_window`` as target. @@ -136,12 +135,11 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: ``args`` will override using the object's ``window_id`` as target. """ if not any(arg.startswith("-t") for arg in args): - args = ("-t", self.window_id) + args + args = ("-t", self.window_id, *args) return self.server.cmd(cmd, *args, **kwargs) # - # Commands (tmux-like) # def select_pane(self, target_pane: t.Union[str, int]) -> t.Optional["Pane"]: """ @@ -219,21 +217,18 @@ def split_window( """ tmux_formats = ["#{pane_id}" + FORMAT_SEPARATOR] - # '-t%s' % self.attached_pane.get('pane_id'), # 2013-10-18 LOOK AT THIS, rm'd it.. - tmux_args: t.Tuple[str, ...] = tuple() + tmux_args: t.Tuple[str, ...] = () if target is not None: tmux_args += ("-t%s" % target,) else: if len(self.panes): tmux_args += ( - "-t%s:%s.%s" - % (self.session_id, self.window_id, self.panes[0].pane_index), + f"-t{self.session_id}:{self.window_id}.{self.panes[0].pane_index}", ) else: - tmux_args += ("-t%s:%s" % (self.session_id, self.window_id),) - # tmux_args += ("-t%s" % self.panes[0].pane_id,) + tmux_args += (f"-t{self.session_id}:{self.window_id}",) if vertical: tmux_args += ("-v",) @@ -313,7 +308,7 @@ def select_layout(self, layout: t.Optional[str] = None) -> "Window": 'custom' custom dimensions (see :term:`tmux(1)` manpages). """ - cmd = ["select-layout", "-t{}:{}".format(self.session_id, self.window_index)] + cmd = ["select-layout", f"-t{self.session_id}:{self.window_index}"] if layout: # tmux allows select-layout without args cmd.append(layout) @@ -349,7 +344,7 @@ def set_window_option(self, option: str, value: t.Union[int, str]) -> "Window": cmd = self.cmd( "set-window-option", - "-t{}:{}".format(self.session_id, self.window_index), + f"-t{self.session_id}:{self.window_index}", option, value, ) @@ -375,7 +370,7 @@ def show_window_options(self, g: t.Optional[bool] = False) -> "WindowOptionDict" g : str, optional Pass ``-g`` flag for global variable, default False. """ - tmux_args: t.Tuple[str, ...] = tuple() + tmux_args: t.Tuple[str, ...] = () if g: tmux_args += ("-g",) @@ -419,7 +414,7 @@ def show_window_option( :exc:`exc.OptionError`, :exc:`exc.UnknownOption`, :exc:`exc.InvalidOption`, :exc:`exc.AmbiguousOption` """ - tmux_args: t.Tuple[t.Union[str, int], ...] = tuple() + tmux_args: t.Tuple[t.Union[str, int], ...] = () if g: tmux_args += ("-g",) @@ -436,7 +431,7 @@ def show_window_option( if not len(window_options_output): return None - value_raw = [shlex.split(item) for item in window_options_output][0] + value_raw = next(shlex.split(item) for item in window_options_output) value: t.Union[str, int] = ( int(value_raw[1]) if value_raw[1].isdigit() else value_raw[1] @@ -486,7 +481,7 @@ def kill_window(self) -> None: proc = self.cmd( "kill-window", - "-t{}:{}".format(self.session_id, self.window_index), + f"-t{self.session_id}:{self.window_index}", ) if proc.stderr: @@ -510,7 +505,7 @@ def move_window( session = session or self.session_id proc = self.cmd( "move-window", - "-s{}:{}".format(self.session_id, self.window_index), + f"-s{self.session_id}:{self.window_index}", f"-t{session}:{destination}", ) diff --git a/tests/legacy_api/test_common.py b/tests/legacy_api/test_common.py index 7e6db2c43..c6a867126 100644 --- a/tests/legacy_api/test_common.py +++ b/tests/legacy_api/test_common.py @@ -62,7 +62,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: assert has_minimum_version() assert has_gte_version(TMUX_MIN_VERSION) assert has_gt_version(TMUX_MAX_VERSION), "Greater than the max-supported version" - assert TMUX_NEXT_VERSION == get_version() + assert get_version() == TMUX_NEXT_VERSION def test_get_version_openbsd(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/tests/legacy_api/test_server.py b/tests/legacy_api/test_server.py index d7830fd0b..9cc03f463 100644 --- a/tests/legacy_api/test_server.py +++ b/tests/legacy_api/test_server.py @@ -71,12 +71,12 @@ def test_show_environment(server: Server) -> None: def test_getenv(server: Server, session: Session) -> None: """Set environment then Server.show_environment(key).""" server.set_environment("FOO", "BAR") - assert "BAR" == server.getenv("FOO") + assert server.getenv("FOO") == "BAR" server.set_environment("FOO", "DAR") - assert "DAR" == server.getenv("FOO") + assert server.getenv("FOO") == "DAR" - assert "DAR" == server.show_environment()["FOO"] + assert server.show_environment()["FOO"] == "DAR" def test_show_environment_not_set(server: Server) -> None: diff --git a/tests/test_common.py b/tests/test_common.py index f5bdccb04..e91492a3e 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -61,7 +61,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: assert has_minimum_version() assert has_gte_version(TMUX_MIN_VERSION) assert has_gt_version(TMUX_MAX_VERSION), "Greater than the max-supported version" - assert TMUX_NEXT_VERSION == get_version() + assert get_version() == TMUX_NEXT_VERSION def test_get_version_openbsd(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 531932aa8..9dbf994e5 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -50,10 +50,9 @@ def test_repo_git_remote_checkout( """ ) } - first_test_key = list(files.keys())[0] + first_test_key = next(iter(files.keys())) first_test_filename = str(tests_path / first_test_key) - # Setup: Files tests_path.mkdir() for file_name, text in files.items(): test_file = tests_path / file_name diff --git a/tests/test_server.py b/tests/test_server.py index 711964e27..9731632fe 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -71,12 +71,12 @@ def test_show_environment(server: Server) -> None: def test_getenv(server: Server, session: Session) -> None: """Set environment then Server.show_environment(key).""" server.set_environment("FOO", "BAR") - assert "BAR" == server.getenv("FOO") + assert server.getenv("FOO") == "BAR" server.set_environment("FOO", "DAR") - assert "DAR" == server.getenv("FOO") + assert server.getenv("FOO") == "DAR" - assert "DAR" == server.show_environment()["FOO"] + assert server.show_environment()["FOO"] == "DAR" def test_show_environment_not_set(server: Server) -> None: From 2fdfc1ceb33e5dabaee43b399121b78fb3a94b70 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 04:28:51 -0500 Subject: [PATCH 04/13] feat(exc): Add window listing related exceptions --- src/libtmux/exc.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index e76c41263..b0e81bbea 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -54,3 +54,32 @@ class AmbiguousOption(OptionError): class WaitTimeout(LibTmuxException): """Function timed out without meeting condition""" + + +class WindowError(LibTmuxException): + + """Any type of window related error""" + + +class MultipleActiveWindows(WindowError): + + """Multiple active windows""" + + def __init__(self, count: int, *args: object): + return super().__init__(f"Multiple active windows: {count} found") + + +class NoActiveWindow(WindowError): + + """No active window found""" + + def __init__(self, *args: object): + return super().__init__("No active windows found") + + +class NoWindowsExist(WindowError): + + """No windows exist for object""" + + def __init__(self, *args: object): + return super().__init__("No windows exist for object") From 959cdf07029416cdc1029ea3caf35e49ff1a183f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 04:41:00 -0500 Subject: [PATCH 05/13] feat(exc): Add UnknownColorOption e.g. invalid Server(color=...) argument --- src/libtmux/exc.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index b0e81bbea..a50ef1956 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -41,6 +41,14 @@ class UnknownOption(OptionError): """Option unknown to tmux show-option(s) or show-window-option(s).""" +class UnknownColorOption(UnknownOption): + + """Unknown color option.""" + + def __init__(self, *args: object): + return super().__init__("Server.colors must equal 88 or 256") + + class InvalidOption(OptionError): """Option invalid to tmux, introduced in tmux v2.4.""" From 000a8967f7fc3b7963b4c013e5423e73e9705895 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 04:45:25 -0500 Subject: [PATCH 06/13] feat(exc): Add Pane related exceptions --- src/libtmux/exc.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index a50ef1956..54f858dda 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -4,6 +4,7 @@ ~~~~~~~~~~~ """ +import typing as t class LibTmuxException(Exception): @@ -64,6 +65,19 @@ class WaitTimeout(LibTmuxException): """Function timed out without meeting condition""" +class PaneError(LibTmuxException): + """Any type of pane related error""" + + +class PaneNotFound(PaneError): + """Pane not found""" + + def __init__(self, pane_id: t.Optional[str] = None, *args: object): + if pane_id is not None: + return super().__init__(f"Pane not found: {pane_id}") + return super().__init__("Pane not found") + + class WindowError(LibTmuxException): """Any type of window related error""" From e246f27d7e581331b067500732a5180e964f8de6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 05:32:23 -0500 Subject: [PATCH 07/13] refactor(exc): Add reason to BadSessionName --- src/libtmux/common.py | 10 +++------- src/libtmux/exc.py | 8 ++++++++ tests/legacy_api/test_common.py | 8 ++++---- tests/test_common.py | 8 ++++---- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/libtmux/common.py b/src/libtmux/common.py index 5e7e25c42..1926f3766 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -453,15 +453,11 @@ def session_check_name(session_name: t.Optional[str]) -> None: Invalid session name. """ if session_name is None or len(session_name) == 0: - raise exc.BadSessionName("tmux session names may not be empty.") + raise exc.BadSessionName(reason="empty", session_name=session_name) elif "." in session_name: - raise exc.BadSessionName( - 'tmux session name "%s" may not contain periods.', session_name - ) + raise exc.BadSessionName(reason="contains periods", session_name=session_name) elif ":" in session_name: - raise exc.BadSessionName( - 'tmux session name "%s" may not contain colons.', session_name - ) + raise exc.BadSessionName(reason="contains colons", session_name=session_name) def handle_option_error(error: str) -> t.Type[exc.OptionError]: diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index 54f858dda..c543c9d1f 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -31,6 +31,14 @@ class BadSessionName(LibTmuxException): """Disallowed session name for tmux (empty, contains periods or colons).""" + def __init__( + self, reason: str, session_name: t.Optional[str] = None, *args: object + ): + msg = f"Bad session name: {reason}" + if session_name is not None: + msg += f" (session name: {session_name})" + return super().__init__(msg) + class OptionError(LibTmuxException): diff --git a/tests/legacy_api/test_common.py b/tests/legacy_api/test_common.py index c6a867126..bd8c815ba 100644 --- a/tests/legacy_api/test_common.py +++ b/tests/legacy_api/test_common.py @@ -184,10 +184,10 @@ def test_tmux_cmd_unicode(session: Session) -> None: @pytest.mark.parametrize( "session_name,raises,exc_msg_regex", [ - ("", True, "may not be empty"), - (None, True, "may not be empty"), - ("my great session.", True, "may not contain periods"), - ("name: great session", True, "may not contain colons"), + ("", True, "empty"), + (None, True, "empty"), + ("my great session.", True, "contains periods"), + ("name: great session", True, "contains colons"), ("new great session", False, None), ("ajf8a3fa83fads,,,a", False, None), ], diff --git a/tests/test_common.py b/tests/test_common.py index e91492a3e..e5e1be1d1 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -189,10 +189,10 @@ class SessionCheckName(t.NamedTuple): @pytest.mark.parametrize( SessionCheckName._fields, [ - SessionCheckName("", True, "may not be empty"), - SessionCheckName(None, True, "may not be empty"), - SessionCheckName("my great session.", True, "may not contain periods"), - SessionCheckName("name: great session", True, "may not contain colons"), + SessionCheckName("", True, "empty"), + SessionCheckName(None, True, "empty"), + SessionCheckName("my great session.", True, "contains periods"), + SessionCheckName("name: great session", True, "contains colons"), SessionCheckName("new great session", False, None), SessionCheckName("ajf8a3fa83fads,,,a", False, None), ], From a6892e2816f6d32e97e39846dea4906aa3388c67 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 05:39:32 -0500 Subject: [PATCH 08/13] feat(exc): Add VariableUnpackingError --- src/libtmux/exc.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index c543c9d1f..34f7c5d4a 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -73,6 +73,14 @@ class WaitTimeout(LibTmuxException): """Function timed out without meeting condition""" +class VariableUnpackingError(LibTmuxException): + + """Error unpacking variable""" + + def __init__(self, variable: t.Optional[t.Any] = None, *args: object): + return super().__init__(f"Unexpected variable: {variable!s}") + + class PaneError(LibTmuxException): """Any type of pane related error""" From 5ede0f71019854f5025f15faf59e614ca131394c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 05:54:56 -0500 Subject: [PATCH 09/13] feat(exc): Add TmuxObjectDoesNotExist --- src/libtmux/exc.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index 34f7c5d4a..0023cb3eb 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -6,6 +6,11 @@ """ import typing as t +from libtmux._internal.query_list import ObjectDoesNotExist + +if t.TYPE_CHECKING: + from libtmux.neo import ListExtraArgs + class LibTmuxException(Exception): @@ -22,6 +27,25 @@ class TmuxCommandNotFound(LibTmuxException): """Application binary for tmux not found.""" +class TmuxObjectDoesNotExist(ObjectDoesNotExist): + """The query returned multiple objects when only one was expected.""" + + def __init__( + self, + obj_key: t.Optional[str] = None, + obj_id: t.Optional[str] = None, + list_cmd: t.Optional[str] = None, + list_extra_args: "t.Optional[ListExtraArgs]" = None, + *args: object, + ): + if all(arg is not None for arg in [obj_key, obj_id, list_cmd, list_extra_args]): + return super().__init__( + f"Could not find {obj_key}={obj_id} for {list_cmd} " + f'{list_extra_args if list_extra_args is not None else ""}' + ) + return super().__init__("Could not find object") + + class VersionTooLow(LibTmuxException): """Raised if tmux below the minimum version to use libtmux.""" From 3f51d4afd3865579eea8e776f75a351a1a89f5ae Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 06:02:53 -0500 Subject: [PATCH 10/13] feat(exc,query_list): New exceptions for lookups --- src/libtmux/_internal/query_list.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libtmux/_internal/query_list.py b/src/libtmux/_internal/query_list.py index 487ca8deb..4154a3844 100644 --- a/src/libtmux/_internal/query_list.py +++ b/src/libtmux/_internal/query_list.py @@ -244,6 +244,16 @@ def lookup_iregex( } +class PKRequiredException(Exception): + def __init__(self, *args: object): + return super().__init__("items() require a pk_key exists") + + +class OpNotFound(ValueError): + def __init__(self, op: str, *args: object): + return super().__init__(f"{op} not in LOOKUP_NAME_MAP") + + class QueryList(List[T]): """Filter list of object/dictionaries. For small, local datasets. From fd54fa18c3d4e797c5514640b732ac047c02484e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 05:51:49 -0500 Subject: [PATCH 11/13] refactor(query_list): Extract exceptions --- src/libtmux/_internal/query_list.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libtmux/_internal/query_list.py b/src/libtmux/_internal/query_list.py index 4154a3844..de5ea5078 100644 --- a/src/libtmux/_internal/query_list.py +++ b/src/libtmux/_internal/query_list.py @@ -59,11 +59,12 @@ def keygetter( elif hasattr(dct, sub_field): dct = getattr(dct, sub_field) - return dct except Exception as e: traceback.print_stack() print(f"Above error was {e}") - return None + return None + + return dct def parse_lookup(obj: "Mapping[str, Any]", path: str, lookup: str) -> Optional[Any]: @@ -294,7 +295,7 @@ class QueryList(List[T]): def items(self) -> List[T]: if self.pk_key is None: - raise Exception("items() require a pk_key exists") + raise PKRequiredException() return [(getattr(item, self.pk_key), item) for item in self] def __eq__( @@ -334,7 +335,7 @@ def filter_lookup(obj: Any) -> bool: lhs, op = path.rsplit("__", 1) if op not in LOOKUP_NAME_MAP: - raise ValueError(f"{op} not in LOOKUP_NAME_MAP") + raise OpNotFound(op=op) except ValueError: lhs = path op = "exact" From c83b07ed817c6b32e58cde834e1073e93d0c994a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 19 Aug 2023 18:44:57 -0500 Subject: [PATCH 12/13] chore(ruff): Manual fixes docs/conf.py:191:9: PERF203 `try`-`except` within a loop incurs performance overhead src/libtmux/_internal/query_list.py:62:9: TRY300 Consider moving this statement to an `else` block src/libtmux/_internal/query_list.py:297:19: TRY002 Create your own exception src/libtmux/_internal/query_list.py:297:19: TRY003 Avoid specifying long messages outside the exception class src/libtmux/_internal/query_list.py:337:25: TRY301 Abstract `raise` to an inner function src/libtmux/_internal/query_list.py:337:31: TRY003 Avoid specifying long messages outside the exception class src/libtmux/common.py:154:23: TRY003 Avoid specifying long messages outside the exception class src/libtmux/common.py:191:23: TRY003 Avoid specifying long messages outside the exception class src/libtmux/common.py:246:13: TRY400 Use `logging.exception` instead of `logging.error` src/libtmux/neo.py:243:15: TRY003 Avoid specifying long messages outside the exception class src/libtmux/pane.py:293:19: TRY003 Avoid specifying long messages outside the exception class src/libtmux/pane.py:439:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/pane.py:446:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/pytest_plugin.py:230:5: TRY302 Remove exception handler; error is immediately re-raised src/libtmux/server.py:146:13: TRY300 Consider moving this statement to an `else` block src/libtmux/server.py:210:23: TRY003 Avoid specifying long messages outside the exception class src/libtmux/server.py:241:13: TRY300 Consider moving this statement to an `else` block src/libtmux/server.py:515:17: PERF401 Use a list comprehension to create a transformed list src/libtmux/server.py:535:13: PERF401 Use a list comprehension to create a transformed list src/libtmux/server.py:553:13: PERF401 Use a list comprehension to create a transformed list src/libtmux/server.py:591:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:604:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:612:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:619:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:629:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:642:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:650:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:660:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:667:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:679:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/server.py:688:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:111:17: PERF401 Use a list comprehension to create a transformed list src/libtmux/session.py:130:17: PERF401 Use a list comprehension to create a transformed list src/libtmux/session.py:361:17: PERF401 Use a list comprehension to create a transformed list src/libtmux/session.py:366:19: TRY003 Avoid specifying long messages outside the exception class src/libtmux/session.py:373:19: TRY003 Avoid specifying long messages outside the exception class src/libtmux/session.py:487:31: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()` src/libtmux/session.py:602:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:609:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:616:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:623:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:633:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:640:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:649:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:657:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/session.py:666:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/test.py:50:15: PTH100 `os.path.abspath()` should be replaced by `Path.resolve()` src/libtmux/test.py:50:31: PTH120 `os.path.dirname()` should be replaced by `Path.parent` src/libtmux/test.py:51:15: PTH100 `os.path.abspath()` should be replaced by `Path.resolve()` src/libtmux/test.py:51:31: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator src/libtmux/test.py:52:33: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator src/libtmux/window.py:123:17: PERF401 Use a list comprehension to create a transformed list src/libtmux/window.py:245:31: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()` src/libtmux/window.py:473:13: TRY400 Use `logging.exception` instead of `logging.error` src/libtmux/window.py:633:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:640:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:647:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:654:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:664:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:671:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:680:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:688:9: B028 No explicit `stacklevel` keyword argument found src/libtmux/window.py:697:9: B028 No explicit `stacklevel` keyword argument found tests/legacy_api/test_common.py:34:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/legacy_api/test_common.py:54:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/legacy_api/test_common.py:70:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/legacy_api/test_common.py:87:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/legacy_api/test_server.py:152:10: B017 `pytest.raises(Exception)` should be considered evil tests/test_common.py:33:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/test_common.py:53:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/test_common.py:69:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/test_common.py:86:18: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` tests/test_server.py:169:10: B017 `pytest.raises(Exception)` should be considered evil tests/test_window.py:206:5: B018 Found useless expression. Either assign it to a variable or remove it. --- docs/conf.py | 2 +- src/libtmux/common.py | 8 ++-- src/libtmux/neo.py | 9 ++-- src/libtmux/pane.py | 6 +-- src/libtmux/pytest_plugin.py | 5 +-- src/libtmux/server.py | 60 +++++++++++++------------- src/libtmux/session.py | 76 ++++++++++++++++----------------- src/libtmux/test.py | 7 +-- src/libtmux/window.py | 47 ++++++++++---------- tests/legacy_api/test_common.py | 8 ++-- tests/legacy_api/test_server.py | 3 +- tests/test_common.py | 8 ++-- tests/test_server.py | 3 +- tests/test_window.py | 2 +- 14 files changed, 124 insertions(+), 120 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index fb4f6517b..a69842357 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -188,7 +188,7 @@ def linkcode_resolve(domain: str, info: t.Dict[str, str]) -> t.Union[None, str]: for part in fullname.split("."): try: obj = getattr(obj, part) - except Exception: + except Exception: # noqa: PERF203 return None # strip decorators, which would resolve to the source of the decorator diff --git a/src/libtmux/common.py b/src/libtmux/common.py index 1926f3766..4c85a3550 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -151,7 +151,7 @@ def show_environment(self) -> Dict[str, Union[bool, str]]: elif len(_t) == 1: vars_dict[_t[0]] = True else: - raise ValueError(f"unexpected variable {_t}") + raise exc.VariableUnpackingError(variable=_t) return vars_dict @@ -188,7 +188,7 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]: elif len(_t) == 1: vars_dict[_t[0]] = True else: - raise ValueError(f"unexpected variable {_t}") + raise exc.VariableUnpackingError(variable=_t) return vars_dict.get(name) @@ -242,8 +242,8 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: ) stdout, stderr = self.process.communicate() returncode = self.process.returncode - except Exception as e: - logger.error(f"Exception for {subprocess.list2cmdline(cmd)}: \n{e}") + except Exception: + logger.exception(f"Exception for {subprocess.list2cmdline(cmd)}") raise self.returncode = returncode diff --git a/src/libtmux/neo.py b/src/libtmux/neo.py index 839345cac..9609e1e32 100644 --- a/src/libtmux/neo.py +++ b/src/libtmux/neo.py @@ -3,7 +3,6 @@ import typing as t from libtmux import exc -from libtmux._internal.query_list import ObjectDoesNotExist from libtmux.common import tmux_cmd from libtmux.formats import FORMAT_SEPARATOR @@ -240,9 +239,11 @@ def fetch_obj( obj = _obj if obj is None: - raise ObjectDoesNotExist( - f"Could not find {obj_key}={obj_id} for {list_cmd} " - f'{list_extra_args if list_extra_args is not None else ""}' + raise exc.TmuxObjectDoesNotExist( + obj_key=obj_key, + obj_id=obj_id, + list_cmd=list_cmd, + list_extra_args=list_extra_args, ) assert obj is not None diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 9d62ac1b6..6ec3f8092 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -290,7 +290,7 @@ def select_pane(self) -> "Pane": assert isinstance(self.pane_id, str) pane = self.window.select_pane(self.pane_id) if pane is None: - raise exc.LibTmuxException(f"Pane not found: {self}") + raise exc.PaneNotFound(pane_id=self.pane_id) return pane def split_window( @@ -436,12 +436,12 @@ def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any: """ .. deprecated:: 0.16 """ - warnings.warn("Pane.get() is deprecated") + warnings.warn("Pane.get() is deprecated", stacklevel=2) return getattr(self, key, default) def __getitem__(self, key: str) -> t.Any: """ .. deprecated:: 0.16 """ - warnings.warn(f"Item lookups, e.g. pane['{key}'] is deprecated") + warnings.warn(f"Item lookups, e.g. pane['{key}'] is deprecated", stacklevel=2) return getattr(self, key) diff --git a/src/libtmux/pytest_plugin.py b/src/libtmux/pytest_plugin.py index 0a33fd8e8..0cfb1c594 100644 --- a/src/libtmux/pytest_plugin.py +++ b/src/libtmux/pytest_plugin.py @@ -225,10 +225,7 @@ def session( TEST_SESSION_NAME = get_test_session_name(server=server) - try: - session = server.new_session(session_name=TEST_SESSION_NAME, **session_params) - except exc.LibTmuxException: - raise + session = server.new_session(session_name=TEST_SESSION_NAME, **session_params) """ Make sure that tmuxp can :ref:`test_builder_visually` and switches to diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 87c08fe35..5f8029492 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -143,9 +143,9 @@ def is_alive(self) -> bool: """ try: res = self.cmd("list-sessions") - return res.returncode == 0 except Exception: return False + return res.returncode == 0 def raise_if_dead(self) -> None: """Raise if server not connected. @@ -207,7 +207,7 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: elif self.colors == 88: cmd_args.insert(0, "-8") else: - raise ValueError("Server.colors must equal 88 or 256") + raise exc.UnknownColorOption() return tmux_cmd(*cmd_args, **kwargs) @@ -238,9 +238,9 @@ def attached_sessions(self) -> t.List[Session]: else: continue - return attached_sessions except Exception: return [] + return attached_sessions def has_session(self, target_session: str, exact: bool = True) -> bool: """ @@ -512,7 +512,7 @@ def sessions(self) -> QueryList[Session]: # type:ignore list_cmd="list-sessions", server=self, ): - sessions.append(Session(server=self, **obj)) + sessions.append(Session(server=self, **obj)) # noqa: PERF401 except Exception: pass @@ -526,13 +526,14 @@ def windows(self) -> QueryList[Window]: # type:ignore :meth:`.windows.get() ` and :meth:`.windows.filter() ` """ - windows: t.List["Window"] = [] - for obj in fetch_objs( - list_cmd="list-windows", - list_extra_args=("-a",), - server=self, - ): - windows.append(Window(server=self, **obj)) + windows: t.List["Window"] = [ + Window(server=self, **obj) + for obj in fetch_objs( + list_cmd="list-windows", + list_extra_args=("-a",), + server=self, + ) + ] return QueryList(windows) @@ -544,13 +545,14 @@ def panes(self) -> QueryList[Pane]: # type:ignore :meth:`.panes.get() ` and :meth:`.panes.filter() ` """ - panes: t.List["Pane"] = [] - for obj in fetch_objs( - list_cmd="list-panes", - list_extra_args=["-s"], - server=self, - ): - panes.append(Pane(server=self, **obj)) + panes: t.List["Pane"] = [ + Pane(server=self, **obj) + for obj in fetch_objs( + list_cmd="list-panes", + list_extra_args=["-s"], + server=self, + ) + ] return QueryList(panes) @@ -588,7 +590,7 @@ def _list_panes(self) -> t.List[PaneDict]: .. deprecated:: 0.16 """ - warnings.warn("Server._list_panes() is deprecated") + warnings.warn("Server._list_panes() is deprecated", stacklevel=2) return [p.__dict__ for p in self.panes] def _update_panes(self) -> "Server": @@ -601,7 +603,7 @@ def _update_panes(self) -> "Server": .. deprecated:: 0.16 """ - warnings.warn("Server._update_panes() is deprecated") + warnings.warn("Server._update_panes() is deprecated", stacklevel=2) self._list_panes() return self @@ -609,14 +611,14 @@ def get_by_id(self, id: str) -> t.Optional[Session]: """ .. deprecated:: 0.16 """ - warnings.warn("Server.get_by_id() is deprecated") + warnings.warn("Server.get_by_id() is deprecated", stacklevel=2) return self.sessions.get(session_id=id, default=None) def where(self, kwargs: t.Dict[str, t.Any]) -> t.List[Session]: """ .. deprecated:: 0.16 """ - warnings.warn("Server.find_where() is deprecated") + warnings.warn("Server.find_where() is deprecated", stacklevel=2) try: return self.sessions.filter(**kwargs) except IndexError: @@ -626,7 +628,7 @@ def find_where(self, kwargs: t.Dict[str, t.Any]) -> t.Optional[Session]: """ .. deprecated:: 0.16 """ - warnings.warn("Server.find_where() is deprecated") + warnings.warn("Server.find_where() is deprecated", stacklevel=2) return self.sessions.get(default=None, **kwargs) def _list_windows(self) -> t.List[WindowDict]: @@ -639,7 +641,7 @@ def _list_windows(self) -> t.List[WindowDict]: .. deprecated:: 0.16 """ - warnings.warn("Server._list_windows() is deprecated") + warnings.warn("Server._list_windows() is deprecated", stacklevel=2) return [w.__dict__ for w in self.windows] def _update_windows(self) -> "Server": @@ -647,7 +649,7 @@ def _update_windows(self) -> "Server": .. deprecated:: 0.16 """ - warnings.warn("Server._update_windows() is deprecated") + warnings.warn("Server._update_windows() is deprecated", stacklevel=2) self._list_windows() return self @@ -657,14 +659,14 @@ def _sessions(self) -> t.List[SessionDict]: .. deprecated:: 0.16 """ - warnings.warn("Server._sessions is deprecated") + warnings.warn("Server._sessions is deprecated", stacklevel=2) return self._list_sessions() def _list_sessions(self) -> t.List["SessionDict"]: """ .. deprecated:: 0.16 """ - warnings.warn("Server._list_sessions() is deprecated") + warnings.warn("Server._list_sessions() is deprecated", stacklevel=2) return [s.__dict__ for s in self.sessions] def list_sessions(self) -> t.List[Session]: @@ -676,7 +678,7 @@ def list_sessions(self) -> t.List[Session]: ------- list of :class:`Session` """ - warnings.warn("Server.list_sessions is deprecated") + warnings.warn("Server.list_sessions is deprecated", stacklevel=2) return self.sessions @property @@ -685,5 +687,5 @@ def children(self) -> QueryList["Session"]: # type:ignore .. deprecated:: 0.16 """ - warnings.warn("Server.children is deprecated") + warnings.warn("Server.children is deprecated", stacklevel=2) return self.sessions diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 52b34a4cf..d1d688d71 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -6,7 +6,7 @@ """ import dataclasses import logging -import os +import pathlib import typing as t import warnings @@ -101,14 +101,15 @@ def windows(self) -> QueryList["Window"]: # type:ignore :meth:`.windows.get() ` and :meth:`.windows.filter() ` """ - windows: t.List["Window"] = [] - for obj in fetch_objs( - list_cmd="list-windows", - list_extra_args=["-t", str(self.session_id)], - server=self.server, - ): - if obj.get("session_id") == self.session_id: - windows.append(Window(server=self.server, **obj)) + windows: t.List["Window"] = [ + Window(server=self.server, **obj) + for obj in fetch_objs( + list_cmd="list-windows", + list_extra_args=["-t", str(self.session_id)], + server=self.server, + ) + if obj.get("session_id") == self.session_id + ] return QueryList(windows) @@ -120,14 +121,15 @@ def panes(self) -> QueryList["Pane"]: # type:ignore :meth:`.panes.get() ` and :meth:`.panes.filter() ` """ - panes: t.List["Pane"] = [] - for obj in fetch_objs( - list_cmd="list-panes", - list_extra_args=["-s", "-t", str(self.session_id)], - server=self.server, - ): - if obj.get("session_id") == self.session_id: - panes.append(Pane(server=self.server, **obj)) + panes: t.List["Pane"] = [ + Pane(server=self.server, **obj) + for obj in fetch_objs( + list_cmd="list-panes", + list_extra_args=["-s", "-t", str(self.session_id)], + server=self.server, + ) + if obj.get("session_id") == self.session_id + ] return QueryList(panes) @@ -354,23 +356,19 @@ def attached_window(self) -> "Window": """ Return active :class:`Window` object. """ - active_windows = [] - for window in self.windows: - # for now window_active is a unicode - if window.window_active == "1": - active_windows.append(window) + active_windows = [ + window for window in self.windows if window.window_active == "1" + ] if len(active_windows) == 1: return next(iter(active_windows)) elif len(active_windows) == 0: - raise exc.LibTmuxException("no active windows found") + raise exc.NoActiveWindow() else: - raise exc.LibTmuxException( - "multiple active windows found. %s" % active_windows - ) + raise exc.MultipleActiveWindows(count=len(active_windows)) if len(self._windows) == 0: - raise exc.LibTmuxException("No Windows") + raise exc.NoWindowsExist() def attach_session(self) -> "Session": """Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``.""" @@ -484,8 +482,8 @@ def new_window( if start_directory: # as of 2014-02-08 tmux 1.9-dev doesn't expand ~ in new-window -c. - start_directory = os.path.expanduser(start_directory) - window_args += ("-c%s" % start_directory,) + start_directory = pathlib.Path(start_directory).expanduser() + window_args += (f"-c{start_directory}",) window_args += ("-F#{window_id}",) # output if window_name is not None and isinstance(window_name, str): @@ -599,28 +597,30 @@ def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any: """ .. deprecated:: 0.16 """ - warnings.warn("Session.get() is deprecated") + warnings.warn("Session.get() is deprecated", stacklevel=2) return getattr(self, key, default) def __getitem__(self, key: str) -> t.Any: """ .. deprecated:: 0.16 """ - warnings.warn(f"Item lookups, e.g. session['{key}'] is deprecated") + warnings.warn( + f"Item lookups, e.g. session['{key}'] is deprecated", stacklevel=2 + ) return getattr(self, key) def get_by_id(self, id: str) -> t.Optional[Window]: """ .. deprecated:: 0.16 """ - warnings.warn("Session.get_by_id() is deprecated") + warnings.warn("Session.get_by_id() is deprecated", stacklevel=2) return self.windows.get(window_id=id, default=None) def where(self, kwargs: t.Dict[str, t.Any]) -> t.List[Window]: """ .. deprecated:: 0.16 """ - warnings.warn("Session.where() is deprecated") + warnings.warn("Session.where() is deprecated", stacklevel=2) try: return self.windows.filter(**kwargs) except IndexError: @@ -630,14 +630,14 @@ def find_where(self, kwargs: t.Dict[str, t.Any]) -> t.Optional[Window]: """ .. deprecated:: 0.16 """ - warnings.warn("Session.find_where() is deprecated") + warnings.warn("Session.find_where() is deprecated", stacklevel=2) return self.windows.get(default=None, **kwargs) def _list_windows(self) -> t.List["WindowDict"]: """ .. deprecated:: 0.16 """ - warnings.warn("Session._list_windows() is deprecated") + warnings.warn("Session._list_windows() is deprecated", stacklevel=2) return [w.__dict__ for w in self.windows] @property @@ -646,7 +646,7 @@ def _windows(self) -> t.List["WindowDict"]: .. deprecated:: 0.16 """ - warnings.warn("Session._windows is deprecated") + warnings.warn("Session._windows is deprecated", stacklevel=2) return self._list_windows() def list_windows(self) -> t.List["Window"]: @@ -654,7 +654,7 @@ def list_windows(self) -> t.List["Window"]: .. deprecated:: 0.16 """ - warnings.warn("Session.list_windows() is deprecated") + warnings.warn("Session.list_windows() is deprecated", stacklevel=2) return self.windows @property @@ -663,5 +663,5 @@ def children(self) -> QueryList["Window"]: # type:ignore .. deprecated:: 0.16 """ - warnings.warn("Session.children is deprecated") + warnings.warn("Session.children is deprecated", stacklevel=2) return self.windows diff --git a/src/libtmux/test.py b/src/libtmux/test.py index c1c996218..57cea49b0 100644 --- a/src/libtmux/test.py +++ b/src/libtmux/test.py @@ -2,6 +2,7 @@ import contextlib import logging import os +import pathlib import random import time import types @@ -47,9 +48,9 @@ def __next__(self) -> str: namer = RandomStrSequence() -current_dir = os.path.abspath(os.path.dirname(__file__)) -example_dir = os.path.abspath(os.path.join(current_dir, "..", "examples")) -fixtures_dir = os.path.realpath(os.path.join(current_dir, "fixtures")) +current_dir = pathlib.Path(__file__) +example_dir = current_dir.parent / "examples" +fixtures_dir = current_dir / "fixtures" def retry_until( diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 6ffbfe2fa..4e3b450c3 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -6,7 +6,7 @@ """ import dataclasses import logging -import os +import pathlib import shlex import typing as t import warnings @@ -113,14 +113,15 @@ def panes(self) -> QueryList["Pane"]: # type: ignore :meth:`.panes.get() ` and :meth:`.panes.filter() ` """ - panes: t.List["Pane"] = [] - for obj in fetch_objs( - list_cmd="list-panes", - list_extra_args=["-t", str(self.window_id)], - server=self.server, - ): - if obj.get("window_id") == self.window_id: - panes.append(Pane(server=self.server, **obj)) + panes: t.List["Pane"] = [ + Pane(server=self.server, **obj) + for obj in fetch_objs( + list_cmd="list-panes", + list_extra_args=["-t", str(self.window_id)], + server=self.server, + ) + if obj.get("window_id") == self.window_id + ] return QueryList(panes) @@ -240,10 +241,10 @@ def split_window( tmux_args += ("-P", "-F%s" % "".join(tmux_formats)) # output - if start_directory: + if start_directory is not None: # as of 2014-02-08 tmux 1.9-dev doesn't expand ~ in new-window -c. - start_directory = os.path.expanduser(start_directory) - tmux_args += ("-c%s" % start_directory,) + start_path = pathlib.Path(start_directory).expanduser() + tmux_args += (f"-c{start_path}",) if not attach: tmux_args += ("-d",) @@ -469,8 +470,8 @@ def rename_window(self, new_name: str) -> "Window": try: self.cmd("rename-window", new_name) self.window_name = new_name - except Exception as e: - logger.error(e) + except Exception: + logger.exception(f"Error renaming window to {new_name}") self.refresh() @@ -630,28 +631,28 @@ def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any: """ .. deprecated:: 0.16 """ - warnings.warn("Window.get() is deprecated") + warnings.warn("Window.get() is deprecated", stacklevel=2) return getattr(self, key, default) def __getitem__(self, key: str) -> t.Any: """ .. deprecated:: 0.16 """ - warnings.warn(f"Item lookups, e.g. window['{key}'] is deprecated") + warnings.warn(f"Item lookups, e.g. window['{key}'] is deprecated", stacklevel=2) return getattr(self, key) def get_by_id(self, id: str) -> t.Optional[Pane]: """ .. deprecated:: 0.16 """ - warnings.warn("Window.get_by_id() is deprecated") + warnings.warn("Window.get_by_id() is deprecated", stacklevel=2) return self.panes.get(pane_id=id, default=None) def where(self, kwargs: t.Dict[str, t.Any]) -> t.List[Pane]: """ .. deprecated:: 0.16 """ - warnings.warn("Window.where() is deprecated") + warnings.warn("Window.where() is deprecated", stacklevel=2) try: return self.panes.filter(**kwargs) except IndexError: @@ -661,14 +662,14 @@ def find_where(self, kwargs: t.Dict[str, t.Any]) -> t.Optional[Pane]: """ .. deprecated:: 0.16 """ - warnings.warn("Window.find_where() is deprecated") + warnings.warn("Window.find_where() is deprecated", stacklevel=2) return self.panes.get(default=None, **kwargs) def _list_panes(self) -> t.List[PaneDict]: """ .. deprecated:: 0.16 """ - warnings.warn("Window._list_panes() is deprecated") + warnings.warn("Window._list_panes() is deprecated", stacklevel=2) return [pane.__dict__ for pane in self.panes] @property @@ -677,7 +678,7 @@ def _panes(self) -> t.List[PaneDict]: .. deprecated:: 0.16 """ - warnings.warn("_panes is deprecated") + warnings.warn("_panes is deprecated", stacklevel=2) return self._list_panes() def list_panes(self) -> t.List["Pane"]: @@ -685,7 +686,7 @@ def list_panes(self) -> t.List["Pane"]: .. deprecated:: 0.16 """ - warnings.warn("list_panes() is deprecated") + warnings.warn("list_panes() is deprecated", stacklevel=2) return self.panes @property @@ -694,5 +695,5 @@ def children(self) -> QueryList["Pane"]: # type:ignore .. deprecated:: 0.16 """ - warnings.warn("Server.children is deprecated") + warnings.warn("Server.children is deprecated", stacklevel=2) return self.panes diff --git a/tests/legacy_api/test_common.py b/tests/legacy_api/test_common.py index bd8c815ba..158049530 100644 --- a/tests/legacy_api/test_common.py +++ b/tests/legacy_api/test_common.py @@ -31,7 +31,7 @@ def test_allows_master_version(monkeypatch: pytest.MonkeyPatch) -> None: class Hi: - stdout = ["tmux master"] + stdout: t.ClassVar = ["tmux master"] stderr = None def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: @@ -51,7 +51,7 @@ def test_allows_next_version(monkeypatch: pytest.MonkeyPatch) -> None: TMUX_NEXT_VERSION = str(float(TMUX_MAX_VERSION) + 0.1) class Hi: - stdout = [f"tmux next-{TMUX_NEXT_VERSION}"] + stdout: t.ClassVar = [f"tmux next-{TMUX_NEXT_VERSION}"] stderr = None def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: @@ -67,7 +67,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: def test_get_version_openbsd(monkeypatch: pytest.MonkeyPatch) -> None: class Hi: - stderr = ["tmux: unknown option -- V"] + stderr: t.ClassVar = ["tmux: unknown option -- V"] def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: return Hi() @@ -84,7 +84,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: def test_get_version_too_low(monkeypatch: pytest.MonkeyPatch) -> None: class Hi: - stderr = ["tmux: unknown option -- V"] + stderr: t.ClassVar = ["tmux: unknown option -- V"] def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: return Hi() diff --git a/tests/legacy_api/test_server.py b/tests/legacy_api/test_server.py index 9cc03f463..af815edb1 100644 --- a/tests/legacy_api/test_server.py +++ b/tests/legacy_api/test_server.py @@ -1,5 +1,6 @@ """Test for libtmux Server object.""" import logging +import subprocess import pytest @@ -149,7 +150,7 @@ def test_with_server_is_alive(server: Server) -> None: def test_no_server_raise_if_dead() -> None: dead_server = Server(socket_name="test_attached_session_no_server") - with pytest.raises(Exception): + with pytest.raises(subprocess.SubprocessError): dead_server.raise_if_dead() diff --git a/tests/test_common.py b/tests/test_common.py index e5e1be1d1..5a1407c86 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -30,7 +30,7 @@ def test_allows_master_version(monkeypatch: pytest.MonkeyPatch) -> None: class Hi: - stdout = ["tmux master"] + stdout: t.ClassVar = ["tmux master"] stderr = None def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: @@ -50,7 +50,7 @@ def test_allows_next_version(monkeypatch: pytest.MonkeyPatch) -> None: TMUX_NEXT_VERSION = str(float(TMUX_MAX_VERSION) + 0.1) class Hi: - stdout = [f"tmux next-{TMUX_NEXT_VERSION}"] + stdout: t.ClassVar = [f"tmux next-{TMUX_NEXT_VERSION}"] stderr = None def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: @@ -66,7 +66,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: def test_get_version_openbsd(monkeypatch: pytest.MonkeyPatch) -> None: class Hi: - stderr = ["tmux: unknown option -- V"] + stderr: t.ClassVar = ["tmux: unknown option -- V"] def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: return Hi() @@ -83,7 +83,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: def test_get_version_too_low(monkeypatch: pytest.MonkeyPatch) -> None: class Hi: - stderr = ["tmux: unknown option -- V"] + stderr: t.ClassVar = ["tmux: unknown option -- V"] def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: return Hi() diff --git a/tests/test_server.py b/tests/test_server.py index 9731632fe..183ceede4 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,5 +1,6 @@ """Test for libtmux Server object.""" import logging +import subprocess import pytest @@ -166,7 +167,7 @@ def test_with_server_is_alive(server: Server) -> None: def test_no_server_raise_if_dead() -> None: dead_server = Server(socket_name="test_attached_session_no_server") - with pytest.raises(Exception): + with pytest.raises(subprocess.CalledProcessError): dead_server.raise_if_dead() diff --git a/tests/test_window.py b/tests/test_window.py index aa9a1cd6a..94e20b480 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -203,7 +203,7 @@ def test_kill_window(session: Session) -> None: w = session.attached_window - w.window_id + assert w.window_id is not None w.kill_window() with pytest.raises(ObjectDoesNotExist): From eb42b5b52ad3aade0f71a91aa18b3368fb0c2b1b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Aug 2023 06:22:20 -0500 Subject: [PATCH 13/13] docs(CHANGES): Note ruff improvements --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index d123080e4..139401e22 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,14 @@ $ pip install --user --upgrade --pre libtmux _Maintenance only, no bug fixes, or new features_ +### Development + +- Code quality improved via [ruff] rules (#488) + + This includes fixes made by hand, and with ruff's automated fixes. Despite + selecting additional rules, which include import sorting, ruff runs nearly + instantaneously when checking the whole codebase. + ## libtmux 0.22.2 (2023-08-20) ### Development