diff --git a/CHANGES b/CHANGES index 5eaaab24b..06a6b895b 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,29 @@ $ pip install --user --upgrade --pre libtmux +### Documentation + +- Various docstring fixes and tweaks (#514) + +### Development + +- Strengthen linting (#514) + + - Add flake8-commas (COM) + + - https://docs.astral.sh/ruff/rules/#flake8-commas-com + - https://pypi.org/project/flake8-commas/ + + - Add flake8-builtins (A) + + - https://docs.astral.sh/ruff/rules/#flake8-builtins-a + - https://pypi.org/project/flake8-builtins/ + + - Add flake8-errmsg (EM) + + - https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + - https://pypi.org/project/flake8-errmsg/ + ### CI - Move CodeQL from advanced configuration file to GitHub's default diff --git a/docs/conf.py b/docs/conf.py index fbc808157..1b804d5c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,7 @@ master_doc = "index" project = about["__title__"] -copyright = about["__copyright__"] +project_copyright = about["__copyright__"] version = "%s" % (".".join(about["__version__"].split("."))[:2]) release = "%s" % (about["__version__"]) @@ -98,7 +98,7 @@ "sidebar/navigation.html", "sidebar/projects.html", "sidebar/scroll-end.html", - ] + ], } # linkify_issues diff --git a/pyproject.toml b/pyproject.toml index f98dcd11d..d283090da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,8 +122,11 @@ select = [ "F", # pyflakes "I", # isort "UP", # pyupgrade + "A", # flake8-builtins "B", # flake8-bugbear "C4", # flake8-comprehensions + "COM", # flake8-commas + "EM", # flake8-errmsg "Q", # flake8-quotes "PTH", # flake8-use-pathlib "SIM", # flake8-simplify @@ -132,6 +135,9 @@ select = [ "RUF", # Ruff-specific rules "D", # pydocstyle ] +ignore = [ + "COM812", # missing trailing comma, ruff format conflict +] [tool.ruff.lint.isort] known-first-party = [ diff --git a/src/libtmux/_internal/query_list.py b/src/libtmux/_internal/query_list.py index 1113a0151..0a12875de 100644 --- a/src/libtmux/_internal/query_list.py +++ b/src/libtmux/_internal/query_list.py @@ -66,7 +66,9 @@ def keygetter( def parse_lookup( - obj: "Mapping[str, t.Any]", path: str, lookup: str + obj: "Mapping[str, t.Any]", + path: str, + lookup: str, ) -> t.Optional[t.Any]: """Check if field lookup key, e.g. "my__path__contains" has comparator, return val. diff --git a/src/libtmux/_vendor/version.py b/src/libtmux/_vendor/version.py index 82cfe5c04..808b317bf 100644 --- a/src/libtmux/_vendor/version.py +++ b/src/libtmux/_vendor/version.py @@ -33,12 +33,18 @@ ], ] CmpKey = Tuple[ - int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType + int, + Tuple[int, ...], + PrePostDevType, + PrePostDevType, + PrePostDevType, + LocalType, ] VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool] _Version = collections.namedtuple( - "_Version", ["epoch", "release", "dev", "pre", "post", "local"] + "_Version", + ["epoch", "release", "dev", "pre", "post", "local"], ) @@ -220,7 +226,8 @@ def __init__(self, version: str) -> None: release=tuple(int(i) for i in match.group("release").split(".")), pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")), post=_parse_letter_version( - match.group("post_l"), match.group("post_n1") or match.group("post_n2") + match.group("post_l"), + match.group("post_n1") or match.group("post_n2"), ), dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), local=_parse_local_version(match.group("local")), @@ -468,7 +475,8 @@ def micro(self) -> int: def _parse_letter_version( - letter: str, number: Union[str, bytes, SupportsInt] + letter: str, + number: Union[str, bytes, SupportsInt], ) -> Optional[Tuple[str, int]]: if letter: # We consider there to be an implicit 0 in a pre-release if there is @@ -529,7 +537,7 @@ def _cmpkey( # re-reverse it back into the correct order and make it a tuple and use # that for our sorting key. _release = tuple( - reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))) + reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))), ) # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. diff --git a/src/libtmux/common.py b/src/libtmux/common.py index 81e5cb514..c8bb6ccc4 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -46,8 +46,7 @@ def __init__(self, add_option: Optional[str] = None) -> None: self._add_option = add_option def set_environment(self, name: str, value: str) -> None: - """ - Set environment ``$ tmux set-environment ``. + """Set environment ``$ tmux set-environment ``. Parameters ---------- @@ -73,8 +72,7 @@ def set_environment(self, name: str, value: str) -> None: raise ValueError("tmux set-environment stderr: %s" % cmd.stderr) def unset_environment(self, name: str) -> None: - """ - Unset environment variable ``$ tmux set-environment -u ``. + """Unset environment variable ``$ tmux set-environment -u ``. Parameters ---------- @@ -139,17 +137,17 @@ def show_environment(self) -> Dict[str, Union[bool, str]]: tmux_args += [self._add_option] cmd = self.cmd(*tmux_args) output = cmd.stdout - vars = [tuple(item.split("=", 1)) for item in output] - vars_dict: t.Dict[str, t.Union[str, bool]] = {} - for _t in vars: + opts = [tuple(item.split("=", 1)) for item in output] + opts_dict: t.Dict[str, t.Union[str, bool]] = {} + for _t in opts: if len(_t) == 2: - vars_dict[_t[0]] = _t[1] + opts_dict[_t[0]] = _t[1] elif len(_t) == 1: - vars_dict[_t[0]] = True + opts_dict[_t[0]] = True else: raise exc.VariableUnpackingError(variable=_t) - return vars_dict + return opts_dict def getenv(self, name: str) -> Optional[t.Union[str, bool]]: """Show environment variable ``$ tmux show-environment -t [session] ``. @@ -176,22 +174,21 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]: tmux_args += (name,) cmd = self.cmd(*tmux_args) output = cmd.stdout - vars = [tuple(item.split("=", 1)) for item in output] - vars_dict: t.Dict[str, t.Union[str, bool]] = {} - for _t in vars: + opts = [tuple(item.split("=", 1)) for item in output] + opts_dict: t.Dict[str, t.Union[str, bool]] = {} + for _t in opts: if len(_t) == 2: - vars_dict[_t[0]] = _t[1] + opts_dict[_t[0]] = _t[1] elif len(_t) == 1: - vars_dict[_t[0]] = True + opts_dict[_t[0]] = True else: raise exc.VariableUnpackingError(variable=_t) - return vars_dict.get(name) + return opts_dict.get(name) class tmux_cmd: - """ - :term:`tmux(1)` command via :py:mod:`subprocess`. + """:term:`tmux(1)` command via :py:mod:`subprocess`. Examples -------- @@ -231,7 +228,9 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: try: self.process = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, ) stdout, stderr = self.process.communicate() returncode = self.process.returncode @@ -258,14 +257,14 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: logger.debug( "self.stdout for {cmd}: {stdout}".format( - cmd=" ".join(cmd), stdout=self.stdout - ) + cmd=" ".join(cmd), + stdout=self.stdout, + ), ) def get_version() -> LooseVersion: - """ - Return tmux version. + """Return tmux version. If tmux is built from git master, the version returned will be the latest version appended with -master, e.g. ``2.4-master``. @@ -285,7 +284,7 @@ def get_version() -> LooseVersion: return LooseVersion("%s-openbsd" % TMUX_MAX_VERSION) raise exc.LibTmuxException( "libtmux supports tmux %s and greater. This system" - " is running tmux 1.3 or earlier." % TMUX_MIN_VERSION + " is running tmux 1.3 or earlier." % TMUX_MIN_VERSION, ) raise exc.VersionTooLow(proc.stderr) @@ -301,8 +300,7 @@ def get_version() -> LooseVersion: def has_version(version: str) -> bool: - """ - Return affirmative if tmux version installed. + """Return True if tmux version installed. Parameters ---------- @@ -318,8 +316,7 @@ def has_version(version: str) -> bool: def has_gt_version(min_version: str) -> bool: - """ - Return affirmative if tmux version greater than minimum. + """Return True if tmux version greater than minimum. Parameters ---------- @@ -335,8 +332,7 @@ def has_gt_version(min_version: str) -> bool: def has_gte_version(min_version: str) -> bool: - """ - Return True if tmux version greater or equal to minimum. + """Return True if tmux version greater or equal to minimum. Parameters ---------- @@ -352,8 +348,7 @@ def has_gte_version(min_version: str) -> bool: def has_lte_version(max_version: str) -> bool: - """ - Return True if tmux version less or equal to minimum. + """Return True if tmux version less or equal to minimum. Parameters ---------- @@ -369,8 +364,7 @@ def has_lte_version(max_version: str) -> bool: def has_lt_version(max_version: str) -> bool: - """ - Return True if tmux version less than minimum. + """Return True if tmux version less than minimum. Parameters ---------- @@ -386,8 +380,7 @@ def has_lt_version(max_version: str) -> bool: def has_minimum_version(raises: bool = True) -> bool: - """ - Return if tmux meets version requirement. Version >1.8 or above. + """Return True if tmux meets version requirement. Version >1.8 or above. Parameters ---------- @@ -416,12 +409,14 @@ def has_minimum_version(raises: bool = True) -> bool: """ if get_version() < LooseVersion(TMUX_MIN_VERSION): if raises: - raise exc.VersionTooLow( + msg = ( "libtmux only supports tmux {} and greater. This system" " has {} installed. Upgrade your tmux to use libtmux.".format( - TMUX_MIN_VERSION, get_version() + TMUX_MIN_VERSION, + get_version(), ) ) + raise exc.VersionTooLow(msg) else: return False return True diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index bf35a73f4..c77604599 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -38,7 +38,7 @@ def __init__( 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 ""}' + f'{list_extra_args if list_extra_args is not None else ""}', ) return super().__init__("Could not find object") @@ -51,7 +51,10 @@ 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 + self, + reason: str, + session_name: t.Optional[str] = None, + *args: object, ): msg = f"Bad session name: {reason}" if session_name is not None: diff --git a/src/libtmux/neo.py b/src/libtmux/neo.py index 3ad975e7d..926da8254 100644 --- a/src/libtmux/neo.py +++ b/src/libtmux/neo.py @@ -170,7 +170,10 @@ def _refresh( ) -> None: assert isinstance(obj_id, str) obj = fetch_obj( - obj_key=obj_key, obj_id=obj_id, list_cmd=list_cmd, server=self.server + obj_key=obj_key, + obj_id=obj_id, + list_cmd=list_cmd, + server=self.server, ) assert obj is not None if obj is not None: @@ -233,7 +236,9 @@ def fetch_obj( ) -> OutputRaw: """Fetch raw data from tmux command.""" obj_formatters_filtered = fetch_objs( - server=server, list_cmd=list_cmd, list_extra_args=list_extra_args + server=server, + list_cmd=list_cmd, + list_extra_args=list_extra_args, ) obj = None diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index c3c49a4f3..907b23205 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -27,8 +27,7 @@ @dataclasses.dataclass() class Pane(Obj): - """ - A :term:`tmux(1)` :term:`Pane` [pane_manual]_. + """:term:`tmux(1)` :term:`Pane` [pane_manual]_. ``Pane`` instances can send commands directly to a pane, or traverse between linked tmux objects. @@ -107,7 +106,7 @@ def session(self) -> "Session": """ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: - """Return :meth:`Server.cmd` defaulting to ``target_pane`` as target. + """Execute tmux subcommand against target pane. See also: :meth:`Server.cmd`. Send command to tmux with :attr:`pane_id` as ``target-pane``. @@ -124,8 +123,7 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: """ def resize_pane(self, *args: t.Any, **kwargs: t.Any) -> "Pane": - """ - ``$ tmux resize-pane`` of pane and return ``self``. + """Resize tmux pane. Parameters ---------- @@ -161,8 +159,7 @@ def capture_pane( start: t.Union["t.Literal['-']", t.Optional[int]] = None, end: t.Union["t.Literal['-']", t.Optional[int]] = None, ) -> t.Union[str, t.List[str]]: - """ - Capture text from pane. + """Capture text from pane. ``$ tmux capture-pane`` to pane. ``$ tmux capture-pane -S -10`` to pane. @@ -248,7 +245,9 @@ def send_keys( @overload def display_message( - self, cmd: str, get_text: "t.Literal[True]" + self, + cmd: str, + get_text: "t.Literal[True]", ) -> t.Union[str, t.List[str]]: ... @@ -257,10 +256,11 @@ def display_message(self, cmd: str, get_text: "t.Literal[False]") -> None: ... def display_message( - self, cmd: str, get_text: bool = False + self, + cmd: str, + get_text: bool = False, ) -> t.Optional[t.Union[str, t.List[str]]]: - """ - ``$ tmux display-message`` to the pane. + """Display message to pane. Displays a message in target-client status line. @@ -286,8 +286,7 @@ def display_message( """ def select_pane(self) -> "Pane": - """ - Select pane. Return ``self``. + """Select pane. To select a window object asynchrously. If a ``pane`` object exists and is no longer longer the current window, ``w.select_pane()`` @@ -306,8 +305,7 @@ def split_window( start_directory: t.Optional[str] = None, percent: t.Optional[int] = None, ) -> "Pane": # New Pane, not self - """ - Split window at pane and return newly created :class:`Pane`. + """Split window at pane and return newly created :class:`Pane`. Parameters ---------- @@ -333,8 +331,7 @@ def split_window( """ def set_width(self, width: int) -> "Pane": - """ - Set width of pane. + """Set pane width. Parameters ---------- @@ -345,8 +342,7 @@ def set_width(self, width: int) -> "Pane": return self def set_height(self, height: int) -> "Pane": - """ - Set height of pane. + """Set pane height. Parameters ---------- @@ -357,8 +353,7 @@ def set_height(self, height: int) -> "Pane": return self def enter(self) -> "Pane": - """ - Send carriage return to pane. + """Send carriage return to pane. ``$ tmux send-keys`` send Enter to the pane. """ diff --git a/src/libtmux/pytest_plugin.py b/src/libtmux/pytest_plugin.py index 4fabf8174..7cd44c91c 100644 --- a/src/libtmux/pytest_plugin.py +++ b/src/libtmux/pytest_plugin.py @@ -185,7 +185,9 @@ def session_params() -> t.Dict[str, t.Any]: @pytest.fixture(scope="function") def session( - request: pytest.FixtureRequest, session_params: t.Dict[str, t.Any], server: Server + request: pytest.FixtureRequest, + session_params: t.Dict[str, t.Any], + server: Server, ) -> "Session": """Return new, temporary :class:`libtmux.Session`. diff --git a/src/libtmux/server.py b/src/libtmux/server.py index b9861add4..06ea1fc28 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -1,4 +1,4 @@ -"""Pythonization of the :term:`tmux(1)` server. +"""Wrapper for :term:`tmux(1)` server. libtmux.server ~~~~~~~~~~~~~~ @@ -38,8 +38,7 @@ class Server(EnvironmentMixin): - """ - The :term:`tmux(1)` :term:`Server` [server_manual]_. + """:term:`tmux(1)` :term:`Server` [server_manual]_. - :attr:`Server.sessions` [:class:`Session`, ...] @@ -136,7 +135,7 @@ def __init__( self.colors = colors def is_alive(self) -> bool: - """If server alive or not. + """Return True if tmux server alive. >>> tmux = Server(socket_name="no_exist") >>> assert not tmux.is_alive() @@ -175,8 +174,7 @@ def raise_if_dead(self) -> None: # Command # def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: - """ - Execute tmux command and return output. + """Execute tmux command, rsepective of socket name and file, return output. Examples -------- @@ -212,8 +210,7 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: @property def attached_sessions(self) -> t.List[Session]: - """ - Return active :class:`Session` objects. + """Return active :class:`Session`s. Examples -------- @@ -242,8 +239,7 @@ def attached_sessions(self) -> t.List[Session]: return attached_sessions def has_session(self, target_session: str, exact: bool = True) -> bool: - """ - Return True if session exists. ``$ tmux has-session``. + """Return True if session exists. Parameters ---------- @@ -275,12 +271,11 @@ def has_session(self, target_session: str, exact: bool = True) -> bool: return False def kill_server(self) -> None: - """``$ tmux kill-server``.""" + """Kill tmux server.""" self.cmd("kill-server") def kill_session(self, target_session: t.Union[str, int]) -> "Server": - """ - Kill the tmux session with ``$ tmux kill-session``, return ``self``. + """Kill tmux session. Parameters ---------- @@ -304,8 +299,7 @@ def kill_session(self, target_session: t.Union[str, int]) -> "Server": return self def switch_client(self, target_session: str) -> None: - """ - ``$ tmux switch-client``. + """Switch tmux client. Parameters ---------- @@ -324,7 +318,7 @@ def switch_client(self, target_session: str) -> None: raise exc.LibTmuxException(proc.stderr) def attach_session(self, target_session: t.Optional[str] = None) -> None: - """``$ tmux attach-session`` aka alias: ``$ tmux attach``. + """Attach tmux session. Parameters ---------- @@ -359,8 +353,7 @@ def new_session( *args: t.Any, **kwargs: t.Any, ) -> Session: - """ - Return :class:`Session` from ``$ tmux new-session``. + """Create new session, returns new :class:`Session`. Uses ``-P`` flag to print session info, ``-F`` for return formatting returns new Session object. @@ -439,7 +432,7 @@ def new_session( logger.info("session %s exists. killed it." % session_name) else: raise exc.TmuxSessionExists( - "Session named %s exists" % session_name + "Session named %s exists" % session_name, ) logger.debug(f"creating session {session_name}") @@ -486,11 +479,12 @@ def new_session( os.environ["TMUX"] = env session_formatters = dict( - zip(["session_id"], session_stdout.split(formats.FORMAT_SEPARATOR)) + zip(["session_id"], session_stdout.split(formats.FORMAT_SEPARATOR)), ) return Session.from_session_id( - server=self, session_id=session_formatters["session_id"] + server=self, + session_id=session_formatters["session_id"], ) # @@ -498,7 +492,7 @@ def new_session( # @property def sessions(self) -> QueryList[Session]: # type:ignore - """Sessions belonging server. + """Sessions contained in server. Can be accessed via :meth:`.sessions.get() ` and @@ -519,7 +513,7 @@ def sessions(self) -> QueryList[Session]: # type:ignore @property def windows(self) -> QueryList[Window]: # type:ignore - """Windows belonging server. + """Windows contained in server's sessions. Can be accessed via :meth:`.windows.get() ` and @@ -538,7 +532,7 @@ def windows(self) -> QueryList[Window]: # type:ignore @property def panes(self) -> QueryList[Pane]: # type:ignore - """Panes belonging server. + """Panes contained in tmux server (across all windows in all sessions). Can be accessed via :meth:`.panes.get() ` and @@ -582,8 +576,7 @@ def __repr__(self) -> str: # Legacy: Redundant stuff we want to remove # def _list_panes(self) -> t.List[PaneDict]: - """ - Return list of panes in :py:obj:`dict` form. + """Return list of panes in :py:obj:`dict` form. Retrieved from ``$ tmux(1) list-panes`` stdout. @@ -599,8 +592,7 @@ def _list_panes(self) -> t.List[PaneDict]: return [p.__dict__ for p in self.panes] def _update_panes(self) -> "Server": - """ - Update internal pane data and return ``self`` for chainability. + """Update internal pane data and return ``self`` for chainability. .. deprecated:: 0.16 @@ -614,7 +606,7 @@ def _update_panes(self) -> "Server": self._list_panes() return self - def get_by_id(self, id: str) -> t.Optional[Session]: + def get_by_id(self, session_id: str) -> t.Optional[Session]: """Return session by id. Deprecated in favor of :meth:`.sessions.get()`. .. deprecated:: 0.16 @@ -623,7 +615,7 @@ def get_by_id(self, id: str) -> t.Optional[Session]: """ warnings.warn("Server.get_by_id() is deprecated", stacklevel=2) - return self.sessions.get(session_id=id, default=None) + return self.sessions.get(session_id=session_id, default=None) def where(self, kwargs: t.Dict[str, t.Any]) -> t.List[Session]: """Filter through sessions, return list of :class:`Session`. diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 1b0969b21..33e04ad5a 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -36,8 +36,7 @@ @dataclasses.dataclass() class Session(Obj, EnvironmentMixin): - """ - A :term:`tmux(1)` :term:`Session` [session_manual]_. + """:term:`tmux(1)` :term:`Session` [session_manual]_. Holds :class:`Window` objects. @@ -78,7 +77,9 @@ def refresh(self) -> None: """Refresh session attributes from tmux.""" assert isinstance(self.session_id, str) return super()._refresh( - obj_key="session_id", obj_id=self.session_id, list_cmd="list-sessions" + obj_key="session_id", + obj_id=self.session_id, + list_cmd="list-sessions", ) @classmethod @@ -97,7 +98,7 @@ def from_session_id(cls, server: "Server", session_id: str) -> "Session": # @property def windows(self) -> QueryList["Window"]: # type:ignore - """Windows belonging session. + """Windows contained by session. Can be accessed via :meth:`.windows.get() ` and @@ -117,7 +118,7 @@ def windows(self) -> QueryList["Window"]: # type:ignore @property def panes(self) -> QueryList["Pane"]: # type:ignore - """Panes belonging session. + """Panes contained by session's windows. Can be accessed via :meth:`.panes.get() ` and @@ -139,8 +140,7 @@ def panes(self) -> QueryList["Pane"]: # type:ignore # Command # def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: - """ - Return :meth:`server.cmd`. + """Execute tmux subcommand against target session. See :meth:`server.cmd`. Returns ------- @@ -171,10 +171,12 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: """ def set_option( - self, option: str, value: t.Union[str, int], _global: bool = False + self, + option: str, + value: t.Union[str, int], + _global: bool = False, ) -> "Session": - """ - Set option ``$ tmux set-option