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 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..a69842357 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 @@ -191,7 +188,7 @@ def linkcode_resolve( 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 @@ -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/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" diff --git a/src/libtmux/_internal/query_list.py b/src/libtmux/_internal/query_list.py index 7fa3d4170..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]: @@ -123,7 +124,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 +184,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 +205,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 @@ -246,6 +245,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. @@ -286,17 +295,13 @@ 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__( 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 @@ -330,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" 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( diff --git a/src/libtmux/common.py b/src/libtmux/common.py index eac5179bc..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 @@ -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: @@ -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 @@ -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 @@ -452,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 e76c41263..0023cb3eb 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -4,6 +4,12 @@ ~~~~~~~~~~~ """ +import typing as t + +from libtmux._internal.query_list import ObjectDoesNotExist + +if t.TYPE_CHECKING: + from libtmux.neo import ListExtraArgs class LibTmuxException(Exception): @@ -21,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.""" @@ -30,6 +55,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): @@ -41,6 +74,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.""" @@ -54,3 +95,53 @@ class AmbiguousOption(OptionError): 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""" + + +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""" + + +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") 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..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 @@ -40,16 +39,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 +178,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 +227,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 @@ -241,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 cd876c04e..6ec3f8092 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. @@ -293,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( @@ -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 @@ -440,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 f24bfdbd0..0cfb1c594 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 [ @@ -224,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 as e: - raise e + session = server.new_session(session_name=TEST_SESSION_NAME, **session_params) """ Make sure that tmuxp can :ref:`test_builder_visually` and switches to @@ -236,16 +234,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..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. @@ -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 @@ -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) @@ -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 @@ -238,10 +238,9 @@ def attached_sessions(self) -> t.List[Session]: else: continue - return attached_sessions - # return [Session(**s) for s in attached_sessions] or None except Exception: return [] + return attached_sessions def has_session(self, target_session: str, exact: bool = True) -> bool: """ @@ -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,) @@ -513,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 @@ -527,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) @@ -545,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) @@ -572,10 +573,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)" # @@ -592,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": @@ -605,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 @@ -613,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: @@ -630,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]: @@ -643,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": @@ -651,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 @@ -661,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]: @@ -680,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 @@ -689,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 1793a687f..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) @@ -151,7 +153,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 +165,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 +198,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 +241,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 +289,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 +304,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) @@ -355,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 list(active_windows)[0] + 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``.""" @@ -476,7 +473,7 @@ def new_window( ------- :class:`Window` """ - window_args: t.Tuple[str, ...] = tuple() + window_args: t.Tuple[str, ...] = () if not attach: window_args += ("-d",) @@ -485,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): @@ -494,8 +491,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: @@ -601,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: @@ -632,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 @@ -648,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"]: @@ -656,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 @@ -665,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 861ac6dda..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( @@ -305,7 +306,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..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,19 +113,19 @@ 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) # - # 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 +136,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 +218,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",) @@ -245,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",) @@ -313,7 +309,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 +345,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 +371,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 +415,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 +432,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] @@ -474,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() @@ -486,7 +482,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 +506,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}", ) @@ -635,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: @@ -666,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 @@ -682,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"]: @@ -690,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 @@ -699,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 7e6db2c43..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: @@ -62,12 +62,12 @@ 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: 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() @@ -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/legacy_api/test_server.py b/tests/legacy_api/test_server.py index d7830fd0b..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 @@ -71,12 +72,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: @@ -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 f5bdccb04..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: @@ -61,12 +61,12 @@ 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: 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() @@ -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), ], 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..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 @@ -71,12 +72,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: @@ -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):