From 8a7859dd540e8d8c986789673cc15427fbf89789 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 31 Jul 2022 12:24:25 -0500 Subject: [PATCH 01/10] build(Makefile): Add monkeytype tasks (make monkeytype_create, apply) --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 9904fc1b8..c9609c7bc 100644 --- a/Makefile +++ b/Makefile @@ -56,3 +56,9 @@ watch_mypy: format_markdown: prettier --parser=markdown -w *.md docs/*.md docs/**/*.md CHANGES + +monkeytype_create: + poetry run monkeytype run `poetry run which py.test` + +monkeytype_apply: + poetry run monkeytype list-modules | xargs -n1 -I{} sh -c 'poetry run monkeytype apply {}' From 87d48077168ad1ed4c591e04f343598a127a7924 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 31 Jul 2022 12:18:41 -0500 Subject: [PATCH 02/10] build(pyproject): Add strict mypy checking --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index fe17d0456..14f4619dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,9 @@ coverage = ["codecov", "coverage", "pytest-cov"] format = ["black", "isort"] lint = ["flake8", "mypy"] +[tool.mypy] +strict = true + [build-system] requires = ["poetry_core>=1.0.0", "setuptools>50"] build-backend = "poetry.core.masonry.api" From 85fe78ce8caf404602fc732caf95b2c2ca32251b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 31 Jul 2022 12:20:37 -0500 Subject: [PATCH 03/10] build(tmuxp): mypy watchers --- .tmuxp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.tmuxp.yaml b/.tmuxp.yaml index 9baae31c7..c1a39a8a1 100644 --- a/.tmuxp.yaml +++ b/.tmuxp.yaml @@ -12,6 +12,7 @@ windows: panes: - focus: true - pane + - make watch_mypy - make watch_test - window_name: docs layout: main-horizontal From 701281b69ab35083e0369e06583d9c16b42ecb29 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 31 Jul 2022 12:21:58 -0500 Subject: [PATCH 04/10] docs(CHANGES): Note strict mypy compliance --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index d240bd77c..e763cbb41 100644 --- a/CHANGES +++ b/CHANGES @@ -61,6 +61,14 @@ $ pip install --user --upgrade --pre libtmux window.show_window_option('DISPLAY') ``` +## What's new + +- **Improved typings** + + Now [`mypy --strict`] compliant ({issue}`383`) + + [`mypy --strict`]: https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-strict + ### Development - Fix incorrect function name `findWhere()` ({issue}`391`) From 2eac680bf7276223d84ce9fb75095aad2833c6f6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 31 Jul 2022 14:57:16 -0500 Subject: [PATCH 05/10] chore(common): Forward typings --- libtmux/common.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/libtmux/common.py b/libtmux/common.py index 940f6eb06..cd4a0a0da 100644 --- a/libtmux/common.py +++ b/libtmux/common.py @@ -13,10 +13,17 @@ import typing as t from collections.abc import MutableMapping from distutils.version import LooseVersion +from typing import Any, Dict, List, Optional, Union from . import exc from ._compat import console_to_str, str_from_console +if t.TYPE_CHECKING: + from libtmux.pane import Pane + from libtmux.session import Session + from libtmux.window import Window + + logger = logging.getLogger(__name__) @@ -40,10 +47,10 @@ class EnvironmentMixin: _add_option = None - def __init__(self, add_option=None): + def __init__(self, add_option: Optional[str] = None) -> None: self._add_option = add_option - def set_environment(self, name, value): + def set_environment(self, name: str, value: str) -> None: """ Set environment ``$ tmux set-environment ``. @@ -67,7 +74,7 @@ def set_environment(self, name, value): proc.stderr = proc.stderr[0] raise ValueError("tmux set-environment stderr: %s" % proc.stderr) - def unset_environment(self, name): + def unset_environment(self, name: str) -> None: """ Unset environment variable ``$ tmux set-environment -u ``. @@ -88,7 +95,7 @@ def unset_environment(self, name): proc.stderr = proc.stderr[0] raise ValueError("tmux set-environment stderr: %s" % proc.stderr) - def remove_environment(self, name): + def remove_environment(self, name: str) -> None: """Remove environment variable ``$ tmux set-environment -r ``. Parameters @@ -108,7 +115,7 @@ def remove_environment(self, name): proc.stderr = proc.stderr[0] raise ValueError("tmux set-environment stderr: %s" % proc.stderr) - def show_environment(self): + def show_environment(self) -> Optional[Dict[str, Union[bool, str]]]: """Show environment ``$ tmux show-environment -t [session]``. Return dict of environment variables for the session. @@ -139,7 +146,7 @@ def show_environment(self): return vars_dict - def getenv(self, name): + def getenv(self, name: str) -> Optional[str]: """Show environment variable ``$ tmux show-environment -t [session] ``. Return the value of a specific variable if the name is specified. @@ -214,7 +221,7 @@ class tmux_cmd: Renamed from ``tmux`` to ``tmux_cmd``. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: tmux_bin = which( "tmux", default_paths=kwargs.get( @@ -279,10 +286,10 @@ class TmuxMappingObject(MutableMapping): ================ ================================== ============== """ - def __getitem__(self, key): + def __getitem__(self, key: str) -> str: return self._info[key] - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: str) -> None: self._info[key] = value self.dirty = True @@ -297,10 +304,10 @@ def keys(self): def __iter__(self): return self._info.__iter__() - def __len__(self): + def __len__(self) -> int: return len(self._info.keys()) - def __getattr__(self, key): + def __getattr__(self, key: str) -> str: try: return self._info[self.formatter_prefix + key] except KeyError: @@ -337,7 +344,9 @@ class TmuxRelationalObject: ================ ================================== ============== """ - def find_where(self, attrs): + def find_where( + self, attrs: Dict[str, str] + ) -> Optional[Union["Pane", "Window", "Session"]]: """Return object on first match. .. versionchanged:: 0.4 @@ -349,7 +358,9 @@ def find_where(self, attrs): except IndexError: return None - def where(self, attrs, first=False): + def where( + self, attrs: Dict[str, str], first: bool = False + ) -> List[Union["Session", "Pane", "Window", t.Any]]: """ Return objects matching child objects properties. @@ -379,7 +390,7 @@ def by(val) -> bool: return target_children[0] return target_children - def get_by_id(self, id): + def get_by_id(self, id: str) -> Optional[Union["Pane", "Window", "Session"]]: """ Return object based on ``child_id_attribute``. @@ -638,7 +649,7 @@ def has_minimum_version(raises: bool = True) -> bool: return True -def session_check_name(session_name: str): +def session_check_name(session_name: str) -> None: """ Raises exception session name invalid, modeled after tmux function. From 5408d13c0cc1e7d9a79d239bee22ccbb55e4bdf4 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 31 Jul 2022 15:03:09 -0500 Subject: [PATCH 06/10] refactor!(typings): MonkeyType typings (automated + black + isort) --- libtmux/pane.py | 27 +++++++++++++++-------- libtmux/server.py | 35 +++++++++++++++--------------- libtmux/session.py | 35 +++++++++++++++++++----------- libtmux/test.py | 10 ++++++--- libtmux/window.py | 39 +++++++++++++++++++-------------- tests/conftest.py | 10 ++++++--- tests/test_common.py | 38 ++++++++++++++++++-------------- tests/test_pane.py | 10 +++++---- tests/test_server.py | 23 ++++++++++---------- tests/test_session.py | 47 +++++++++++++++++++++------------------- tests/test_test.py | 23 ++++++++++---------- tests/test_tmuxobject.py | 11 +++++----- tests/test_window.py | 43 +++++++++++++++++++----------------- 13 files changed, 201 insertions(+), 150 deletions(-) diff --git a/libtmux/pane.py b/libtmux/pane.py index d944105fe..53cc4acef 100644 --- a/libtmux/pane.py +++ b/libtmux/pane.py @@ -7,6 +7,9 @@ """ import logging import typing as t +from typing import Dict + +from libtmux.common import tmux_cmd from . import exc from .common import TmuxMappingObject, TmuxRelationalObject @@ -58,7 +61,7 @@ class Pane(TmuxMappingObject, TmuxRelationalObject): server: "Server" """:class:`libtmux.Server` pane is linked to""" - def __init__(self, window: "Window", **kwargs): + def __init__(self, window: "Window", **kwargs) -> None: self.window = window self.session = self.window.session self.server = self.session.server @@ -68,7 +71,7 @@ def __init__(self, window: "Window", **kwargs): self.server._update_panes() @property - def _info(self): + def _info(self) -> Dict[str, str]: attrs = {"pane_id": self._pane_id} @@ -86,7 +89,7 @@ def by(val) -> bool: target_panes = list(filter(by, self.server._panes)) return target_panes[0] - def cmd(self, cmd, *args, **kwargs): + def cmd(self, cmd: str, *args, **kwargs) -> tmux_cmd: """Return :meth:`Server.cmd` defaulting to ``target_pane`` as target. Send command to tmux with :attr:`pane_id` as ``target-pane``. @@ -103,7 +106,13 @@ def cmd(self, cmd, *args, **kwargs): return self.server.cmd(cmd, *args, **kwargs) - def send_keys(self, cmd, enter=True, suppress_history=True, literal=False): + def send_keys( + self, + cmd: str, + enter: bool = True, + suppress_history: bool = True, + literal: bool = False, + ) -> None: """ ``$ tmux send-keys`` to the pane. @@ -159,7 +168,7 @@ def clear(self): """Clear pane.""" self.send_keys("reset") - def reset(self): + def reset(self) -> None: """Reset and clear pane history.""" self.cmd("send-keys", r"-R \; clear-history") @@ -193,7 +202,7 @@ def split_window( percent=percent, ) - def set_width(self, width): + def set_width(self, width: int) -> None: """ Set width of pane. @@ -204,7 +213,7 @@ def set_width(self, width): """ self.resize_pane(width=width) - def set_height(self, height): + def set_height(self, height: int) -> None: """ Set height of pane. @@ -215,7 +224,7 @@ def set_height(self, height): """ self.resize_pane(height=height) - def resize_pane(self, *args, **kwargs): + def resize_pane(self, *args, **kwargs) -> "Pane": """ ``$ tmux resize-pane`` of pane and return ``self``. @@ -253,7 +262,7 @@ def resize_pane(self, *args, **kwargs): self.server._update_panes() return self - def enter(self): + def enter(self) -> None: """ Send carriage return to pane. diff --git a/libtmux/server.py b/libtmux/server.py index d31f39296..52f22bf70 100644 --- a/libtmux/server.py +++ b/libtmux/server.py @@ -8,6 +8,9 @@ import os import typing as t +from libtmux.common import tmux_cmd +from libtmux.session import Session + from . import exc, formats from .common import ( EnvironmentMixin, @@ -17,9 +20,7 @@ WindowDict, has_gte_version, session_check_name, - tmux_cmd, ) -from .session import Session logger = logging.getLogger(__name__) @@ -75,12 +76,12 @@ class Server(TmuxRelationalObject, EnvironmentMixin): def __init__( self, - socket_name=None, - socket_path=None, - config_file=None, - colors=None, + socket_name: t.Optional[str] = None, + socket_path: t.Optional[str] = None, + config_file: t.Optional[str] = None, + colors: t.Optional[int] = None, **kwargs, - ): + ) -> None: EnvironmentMixin.__init__(self, "-g") self._windows = [] self._panes = [] @@ -97,7 +98,7 @@ def __init__( if colors: self.colors = colors - def cmd(self, *args, **kwargs): + def cmd(self, *args, **kwargs) -> tmux_cmd: """ Execute tmux command and return output. @@ -382,7 +383,7 @@ def has_session(self, target_session: str, exact: bool = True) -> bool: return False - def kill_server(self): + def kill_server(self) -> None: """``$ tmux kill-server``.""" self.cmd("kill-server") @@ -413,7 +414,7 @@ def kill_session(self, target_session=None) -> "Server": return self - def switch_client(self, target_session): + def switch_client(self, target_session: str): """ ``$ tmux switch-client``. @@ -433,7 +434,7 @@ def switch_client(self, target_session): if proc.stderr: raise exc.LibTmuxException(proc.stderr) - def attach_session(self, target_session=None): + def attach_session(self, target_session: t.Optional[str] = None): """``$ tmux attach-session`` aka alias: ``$ tmux attach``. Parameters @@ -458,12 +459,12 @@ def attach_session(self, target_session=None): def new_session( self, - session_name=None, - kill_session=False, - attach=False, - start_directory=None, - window_name=None, - window_command=None, + session_name: t.Optional[str] = None, + kill_session: bool = False, + attach: bool = False, + start_directory: None = None, + window_name: None = None, + window_command: t.Optional[str] = None, *args, **kwargs, ) -> Session: diff --git a/libtmux/session.py b/libtmux/session.py index 78d2b25e3..fc31c775f 100644 --- a/libtmux/session.py +++ b/libtmux/session.py @@ -7,6 +7,10 @@ import logging import os import typing as t +from typing import Dict, Optional, Union + +from libtmux.common import tmux_cmd +from libtmux.window import Window from . import exc, formats from .common import ( @@ -18,7 +22,6 @@ has_version, session_check_name, ) -from .window import Window if t.TYPE_CHECKING: from .pane import Pane @@ -58,7 +61,7 @@ class Session(TmuxMappingObject, TmuxRelationalObject, EnvironmentMixin): server: "Server" """:class:`libtmux.server.Server` session is linked to""" - def __init__(self, server: "Server", **kwargs): + def __init__(self, server: "Server", **kwargs) -> None: EnvironmentMixin.__init__(self) self.server = server @@ -68,7 +71,7 @@ def __init__(self, server: "Server", **kwargs): self.server._update_windows() @property - def _info(self): + def _info(self) -> Dict[str, str]: attrs = {"session_id": str(self._session_id)} @@ -88,7 +91,7 @@ def by(val) -> bool: except IndexError as e: logger.error(e) - def cmd(self, *args, **kwargs): + def cmd(self, *args, **kwargs) -> tmux_cmd: """ Return :meth:`server.cmd`. @@ -177,11 +180,11 @@ def rename_session(self, new_name: str) -> "Session": def new_window( self, - window_name=None, - start_directory=None, - attach=True, - window_index="", - window_shell=None, + window_name: Optional[str] = None, + start_directory: None = None, + attach: bool = True, + window_index: str = "", + window_shell: None = None, ) -> Window: """ Return :class:`Window` from ``$ tmux new-window``. @@ -259,7 +262,7 @@ def new_window( return window - def kill_window(self, target_window=None): + def kill_window(self, target_window: Optional[str] = None) -> None: """Close a tmux window, and all panes inside it, ``$ tmux kill-window`` Kill the current window or the window at ``target-window``. removing it @@ -378,7 +381,9 @@ def attached_pane(self) -> t.Optional["Pane"]: return self.attached_window.attached_pane - def set_option(self, option, value, _global=False): + def set_option( + self, option: str, value: Union[str, int], _global: bool = False + ) -> None: """ Set option ``$ tmux set-option