From cd8f3c5a933584103ce8bdd8756afee56bac9e59 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 30 Dec 2022 18:08:50 -0600 Subject: [PATCH 01/39] ci(mypy): mypy --strict --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 72ea5456235..878e14d07af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,6 +138,7 @@ exclude_lines = [ ] [tool.mypy] +strict = true files = [ "src/", "tests/", From 0d0cae4ceffa2d7373a6a23d50c7348c4727415c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 06:45:32 -0500 Subject: [PATCH 02/39] ci(mypy): Enable incomplete feature (Unpack) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 878e14d07af..d3ece281a0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,6 +143,7 @@ files = [ "src/", "tests/", ] +enable_incomplete_feature = ["Unpack"] [[tool.mypy.overrides]] module = [ From fe3e169dff5e8cee713d88ae328f8741fb18977a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 30 Dec 2022 18:44:53 -0600 Subject: [PATCH 03/39] refactor!: Add typings from monkeytype --- src/tmuxp/workspace/builder.py | 2 +- src/tmuxp/workspace/freezer.py | 2 +- src/tmuxp/workspace/loader.py | 12 ++-- tests/cli/test_cli.py | 4 +- tests/cli/test_debug_info.py | 2 +- tests/cli/test_import.py | 2 +- tests/cli/test_load.py | 19 +++--- tests/cli/test_ls.py | 2 +- tests/cli/test_shell.py | 6 +- tests/fixtures/workspace/expand1.py | 3 +- tests/fixtures/workspace/expand2.py | 4 +- tests/workspace/test_builder.py | 94 ++++++++++++++++------------- tests/workspace/test_config.py | 24 ++++---- tests/workspace/test_finder.py | 2 +- tests/workspace/test_freezer.py | 10 ++- 15 files changed, 105 insertions(+), 83 deletions(-) diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index b73a250fecd..f149d87d4d7 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -188,7 +188,7 @@ def __init__( pass @property - def session(self): + def session(self) -> Session: if self._session is None: raise exc.SessionMissingWorkspaceException() return self._session diff --git a/src/tmuxp/workspace/freezer.py b/src/tmuxp/workspace/freezer.py index 8f38e7be4bd..8df2e0ee2b5 100644 --- a/src/tmuxp/workspace/freezer.py +++ b/src/tmuxp/workspace/freezer.py @@ -7,7 +7,7 @@ from libtmux.window import Window -def inline(workspace_dict): +def inline(workspace_dict: t.Dict[str, t.Any]) -> t.Any: """Return workspace with inlined shorthands. Opposite of :meth:`loader.expand`. Parameters diff --git a/src/tmuxp/workspace/loader.py b/src/tmuxp/workspace/loader.py index 1668a554547..9d7ee1d9b71 100644 --- a/src/tmuxp/workspace/loader.py +++ b/src/tmuxp/workspace/loader.py @@ -7,7 +7,7 @@ import logging import os import pathlib -from typing import Dict +import typing as t logger = logging.getLogger(__name__) @@ -30,7 +30,7 @@ def expandshell(value: str) -> str: return os.path.expandvars(os.path.expanduser(value)) # NOQA: PTH111 -def expand_cmd(p: Dict) -> Dict: +def expand_cmd(p: t.Dict) -> t.Dict: if isinstance(p, str): p = {"shell_command": [p]} elif isinstance(p, list): @@ -66,7 +66,11 @@ def expand_cmd(p: Dict) -> Dict: return p -def expand(workspace_dict, cwd=None, parent=None): +def expand( + workspace_dict: t.Dict[str, t.Any], + cwd: t.Optional[t.Union[pathlib.Path, str]] = None, + parent: t.Optional[t.Any] = None, +) -> t.Dict[str, t.Any]: """Return workspace with shorthand and inline properties expanded. This is necessary to keep the code in the :class:`WorkspaceBuilder` clean @@ -185,7 +189,7 @@ def expand(workspace_dict, cwd=None, parent=None): return workspace_dict -def trickle(workspace_dict): +def trickle(workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: """Return a dict with "trickled down" / inherited workspace values. This will only work if workspace has been expanded to full form with diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index ea49fb59fe7..b8870be2e2b 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -40,7 +40,7 @@ def test_help( cli_args: t.List[str], tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: # In scrunched terminals, prevent width causing differantiation in result.out. monkeypatch.setenv("COLUMNS", "100") @@ -86,7 +86,7 @@ def test_get_teamocil_dir(monkeypatch: pytest.MonkeyPatch) -> None: def test_pass_config_dir_ClickPath( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: configdir = tmp_path / "myconfigdir" configdir.mkdir() diff --git a/tests/cli/test_debug_info.py b/tests/cli/test_debug_info.py index 0dfd23f19cc..df76fc830df 100644 --- a/tests/cli/test_debug_info.py +++ b/tests/cli/test_debug_info.py @@ -8,7 +8,7 @@ def test_debug_info_cli( monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: monkeypatch.setenv("SHELL", "/bin/bash") diff --git a/tests/cli/test_import.py b/tests/cli/test_import.py index ac8c295a4d9..b08f78a438d 100644 --- a/tests/cli/test_import.py +++ b/tests/cli/test_import.py @@ -15,7 +15,7 @@ def test_import( cli_args: t.List[str], tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: cli.cli(cli_args) result = capsys.readouterr() diff --git a/tests/cli/test_load.py b/tests/cli/test_load.py index aa341245ab3..cfae28cf45e 100644 --- a/tests/cli/test_load.py +++ b/tests/cli/test_load.py @@ -262,7 +262,7 @@ def test_load( tmuxp_configdir: pathlib.Path, server: "Server", session: Session, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, test_id: str, cli_args: t.List[str], @@ -318,7 +318,7 @@ def test_regression_00132_session_name_with_dots( tmp_path: pathlib.Path, server: "Server", session: Session, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: yaml_config = FIXTURE_PATH / "workspace/builder" / "regression_00132_dots.yaml" cli_args = [str(yaml_config)] @@ -333,7 +333,7 @@ def test_load_zsh_autotitle_warning( cli_args: t.List[str], tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], server: "Server", ) -> None: # create dummy tmuxp yaml so we don't get yelled at @@ -392,7 +392,7 @@ def test_load_log_file( cli_args: t.List[str], tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: # create dummy tmuxp yaml that breaks to prevent actually loading tmux tmuxp_config_path = tmp_path / ".tmuxp.yaml" @@ -450,7 +450,10 @@ def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: ], ) def test_load_plugins_version_fail_skip( - monkeypatch_plugin_test_packages, cli_args, inputs, capsys: pytest.CaptureFixture + monkeypatch_plugin_test_packages, + cli_args, + inputs, + capsys: pytest.CaptureFixture[str], ) -> None: with contextlib.suppress(SystemExit): cli.cli(cli_args) @@ -474,7 +477,7 @@ def test_load_plugins_version_fail_no_skip( cli_args: t.List[str], inputs: t.List[str], monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) @@ -493,7 +496,7 @@ def test_load_plugins_version_fail_no_skip( def test_load_plugins_plugin_missing( monkeypatch_plugin_test_packages: None, cli_args: t.List[str], - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: with contextlib.suppress(SystemExit): cli.cli(cli_args) @@ -519,7 +522,7 @@ def test_plugin_system_before_script( session_file, socket_name=server.socket_name, detached=True ) - assert isinstance(session, libtmux.Session) + assert isinstance(session, Session) assert session.name == "plugin_test_bs" diff --git a/tests/cli/test_ls.py b/tests/cli/test_ls.py index 89579d70937..f8a2113ec9c 100644 --- a/tests/cli/test_ls.py +++ b/tests/cli/test_ls.py @@ -9,7 +9,7 @@ def test_ls_cli( monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config")) diff --git a/tests/cli/test_shell.py b/tests/cli/test_shell.py index c95d918c919..d4d9a7cb621 100644 --- a/tests/cli/test_shell.py +++ b/tests/cli/test_shell.py @@ -90,7 +90,7 @@ def test_shell( session: Session, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: monkeypatch.setenv("HOME", str(tmp_path)) window_name = "my_window" @@ -181,7 +181,7 @@ def test_shell_target_missing( session: Session, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: monkeypatch.setenv("HOME", str(tmp_path)) window_name = "my_window" @@ -257,7 +257,7 @@ def test_shell_interactive( session: Session, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: monkeypatch.setenv("HOME", str(tmp_path)) window_name = "my_window" diff --git a/tests/fixtures/workspace/expand1.py b/tests/fixtures/workspace/expand1.py index fa898a06b14..540229b77a6 100644 --- a/tests/fixtures/workspace/expand1.py +++ b/tests/fixtures/workspace/expand1.py @@ -1,4 +1,5 @@ import pathlib +import typing as t before_workspace = { "session_name": "sample workspace", @@ -30,7 +31,7 @@ } -def after_workspace(): +def after_workspace() -> t.Dict[str, t.Any]: return { "session_name": "sample workspace", "start_directory": str(pathlib.Path().home()), diff --git a/tests/fixtures/workspace/expand2.py b/tests/fixtures/workspace/expand2.py index 21f30403748..aac7d52378a 100644 --- a/tests/fixtures/workspace/expand2.py +++ b/tests/fixtures/workspace/expand2.py @@ -3,11 +3,11 @@ from .. import utils as test_utils -def unexpanded_yaml(): +def unexpanded_yaml() -> str: return test_utils.read_workspace_file("workspace/expand2-unexpanded.yaml") -def expanded_yaml(): +def expanded_yaml() -> str: return test_utils.read_workspace_file("workspace/expand2-expanded.yaml").format( HOME=str(pathlib.Path().home()) ) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index a945b73898c..cac1f091093 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -12,6 +12,7 @@ from libtmux.common import has_gte_version, has_lt_version from libtmux.exc import LibTmuxException from libtmux.pane import Pane +from libtmux.server import Server from libtmux.session import Session from libtmux.test import retry_until, temp_session from libtmux.window import Window @@ -26,14 +27,13 @@ from ..fixtures import utils as test_utils if t.TYPE_CHECKING: - from libtmux.server import Server class AssertCallbackProtocol(t.Protocol): def __call__(self, cmd: str, hist: str) -> bool: ... -def test_split_windows(session): +def test_split_windows(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/two_pane.yaml") ) @@ -53,7 +53,7 @@ def test_split_windows(session): window_count += 1 -def test_split_windows_three_pane(session): +def test_split_windows_three_pane(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/three_pane.yaml") ) @@ -75,7 +75,7 @@ def test_split_windows_three_pane(session): w.select_layout(wconf["layout"]) -def test_focus_pane_index(session): +def test_focus_pane_index(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/focus_and_pane.yaml") ) @@ -205,7 +205,7 @@ def f(p: Pane, buffer_name: str, assertCase: AssertCallbackProtocol) -> bool: assert retry_until(_f), f"Unknown sent command: [{sent_cmd}] in {assertCase}" -def test_session_options(session): +def test_session_options(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/session_options.yaml") ) @@ -223,7 +223,7 @@ def test_session_options(session): assert "/bin/sh" in _default_command -def test_global_options(session): +def test_global_options(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/global_options.yaml") ) @@ -238,7 +238,9 @@ def test_global_options(session): assert session.show_option("repeat-time", _global=True) == 493 -def test_global_session_env_options(session, monkeypatch): +def test_global_session_env_options( + session: Session, monkeypatch: pytest.MonkeyPatch +) -> None: visual_silence = "on" monkeypatch.setenv("VISUAL_SILENCE", str(visual_silence)) repeat_time = 738 @@ -263,7 +265,7 @@ def test_global_session_env_options(session, monkeypatch): ) -def test_window_options(session): +def test_window_options(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_options.yaml") ) @@ -292,7 +294,7 @@ def test_window_options(session): @pytest.mark.flaky(reruns=5) -def test_window_options_after(session): +def test_window_options_after(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_options_after.yaml") ) @@ -330,7 +332,7 @@ def f(): ), "Synchronized command did not execute properly" -def test_window_shell(session): +def test_window_shell(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_shell.yaml") ) @@ -356,7 +358,7 @@ def f(w: Window) -> bool: has_lt_version("3.0"), reason="needs -e flag for new-window and split-window introduced in tmux 3.0", ) -def test_environment_variables(session): +def test_environment_variables(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") ) @@ -486,7 +488,7 @@ def check_window_name_match() -> bool: assert retry_until(check_window_name_mismatch, 2, interval=0.25) -def test_blank_pane_count(session): +def test_blank_pane_count(session: Session) -> None: """:todo: Verify blank panes of various types build into workspaces.""" yaml_workspace_file = EXAMPLE_PATH / "blank-panes.yaml" test_config = ConfigReader._from_file(yaml_workspace_file) @@ -514,7 +516,7 @@ def test_blank_pane_count(session): assert len(window4.panes) == 2 -def test_start_directory(session, tmp_path: pathlib.Path): +def test_start_directory(session: Session, tmp_path: pathlib.Path) -> None: test_dir = tmp_path / "foo bar" test_dir.mkdir() @@ -548,7 +550,7 @@ def f(path: str, p: Pane) -> bool: assert retry_until(_f) -def test_start_directory_relative(session, tmp_path: pathlib.Path): +def test_start_directory_relative(session: Session, tmp_path: pathlib.Path) -> None: """Same as above test, but with relative start directory, mimicking loading it from a location of project file. Like:: @@ -604,7 +606,7 @@ def f(path: str, p: Pane) -> bool: @pytest.mark.skipif( has_lt_version("3.2a"), reason="needs format introduced in tmux >= 3.2a" ) -def test_start_directory_sets_session_path(server): +def test_start_directory_sets_session_path(server: Server) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file( "workspace/builder/start_directory_session_path.yaml" @@ -623,7 +625,7 @@ def test_start_directory_sets_session_path(server): assert expected in cmd.stdout -def test_pane_order(session): +def test_pane_order(session: Session) -> None: """Pane ordering based on position in config and ``pane_index``. Regression test for https://github.com/tmux-python/tmuxp/issues/15. @@ -677,7 +679,7 @@ def f(pane_path: str, p: Pane): assert retry_until(_f) -def test_window_index(session): +def test_window_index(session: Session) -> None: proc = session.cmd("show-option", "-gv", "base-index") base_index = int(proc.stdout[0]) name_index_map = {"zero": 0 + base_index, "one": 1 + base_index, "five": 5} @@ -695,7 +697,7 @@ def test_window_index(session): assert int(window.window_index) == expected_index -def test_before_load_throw_error_if_retcode_error(server): +def test_before_load_throw_error_if_retcode_error(server: Server) -> None: config_script_fails = test_utils.read_workspace_file( "workspace/builder/config_script_fails.yaml" ) @@ -719,7 +721,7 @@ def test_before_load_throw_error_if_retcode_error(server): assert not result, "Kills session if before_script exits with errcode" -def test_before_load_throw_error_if_file_not_exists(server): +def test_before_load_throw_error_if_file_not_exists(server: Server) -> None: config_script_not_exists = test_utils.read_workspace_file( "workspace/builder/config_script_not_exists.yaml" ) @@ -743,7 +745,7 @@ def test_before_load_throw_error_if_file_not_exists(server): assert not result, "Kills session if before_script doesn't exist" -def test_before_load_true_if_test_passes(server): +def test_before_load_true_if_test_passes(server: Server) -> None: config_script_completes = test_utils.read_workspace_file( "workspace/builder/config_script_completes.yaml" ) @@ -761,7 +763,7 @@ def test_before_load_true_if_test_passes(server): builder.build(session=session) -def test_before_load_true_if_test_passes_with_args(server): +def test_before_load_true_if_test_passes_with_args(server: Server) -> None: config_script_completes = test_utils.read_workspace_file( "workspace/builder/config_script_completes.yaml" ) @@ -781,8 +783,8 @@ def test_before_load_true_if_test_passes_with_args(server): def test_plugin_system_before_workspace_builder( - monkeypatch_plugin_test_packages, session -): + monkeypatch_plugin_test_packages: None, session: Session +) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/plugin_bwb.yaml") ) @@ -799,7 +801,9 @@ def test_plugin_system_before_workspace_builder( assert proc.stdout[0] == "'plugin_test_bwb'" -def test_plugin_system_on_window_create(monkeypatch_plugin_test_packages, session): +def test_plugin_system_on_window_create( + monkeypatch_plugin_test_packages: None, session: Session +) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/plugin_owc.yaml") ) @@ -816,7 +820,9 @@ def test_plugin_system_on_window_create(monkeypatch_plugin_test_packages, sessio assert proc.stdout[0] == "'plugin_test_owc'" -def test_plugin_system_after_window_finished(monkeypatch_plugin_test_packages, session): +def test_plugin_system_after_window_finished( + monkeypatch_plugin_test_packages: None, session: Session +) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/plugin_awf.yaml") ) @@ -833,7 +839,7 @@ def test_plugin_system_after_window_finished(monkeypatch_plugin_test_packages, s assert proc.stdout[0] == "'plugin_test_awf'" -def test_plugin_system_on_window_create_multiple_windows(session): +def test_plugin_system_on_window_create_multiple_windows(session: Session) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file( "workspace/builder/plugin_owc_multiple_windows.yaml" @@ -854,8 +860,8 @@ def test_plugin_system_on_window_create_multiple_windows(session): def test_plugin_system_after_window_finished_multiple_windows( - monkeypatch_plugin_test_packages, session -): + monkeypatch_plugin_test_packages: None, session: Session +) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file( "workspace/builder/plugin_awf_multiple_windows.yaml" @@ -875,7 +881,9 @@ def test_plugin_system_after_window_finished_multiple_windows( assert "'plugin_test_awf_mw_2'" in proc.stdout -def test_plugin_system_multiple_plugins(monkeypatch_plugin_test_packages, session): +def test_plugin_system_multiple_plugins( + monkeypatch_plugin_test_packages: None, session: Session +) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file( "workspace/builder/plugin_multiple_plugins.yaml" @@ -901,7 +909,7 @@ def test_plugin_system_multiple_plugins(monkeypatch_plugin_test_packages, sessio assert proc.stdout[0] == "'mp_test_awf'" -def test_load_configs_same_session(server): +def test_load_configs_same_session(server: Server) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) @@ -932,7 +940,7 @@ def test_load_configs_same_session(server): assert len(server.sessions[1].windows) == 4 -def test_load_configs_separate_sessions(server): +def test_load_configs_separate_sessions(server: Server) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) @@ -955,7 +963,7 @@ def test_load_configs_separate_sessions(server): assert len(server.sessions[1].windows) == 2 -def test_find_current_active_pane(server, monkeypatch): +def test_find_current_active_pane(server: Server, monkeypatch: pytest.MonkeyPatch) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) @@ -1105,12 +1113,12 @@ def test_find_current_active_pane(server, monkeypatch): ) def test_load_workspace_enter( tmp_path: pathlib.Path, - server: libtmux.Server, + server: Server, monkeypatch: pytest.MonkeyPatch, - yaml, - output, - should_see, -): + yaml: str, + output: str, + should_see: bool, +) -> None: yaml_workspace = tmp_path / "simple.yaml" yaml_workspace.write_text(yaml, encoding="utf-8") workspace = ConfigReader._from_file(yaml_workspace) @@ -1227,12 +1235,12 @@ def fn(): @pytest.mark.flaky(reruns=3) def test_load_workspace_sleep( tmp_path: pathlib.Path, - server: libtmux.Server, + server: Server, monkeypatch: pytest.MonkeyPatch, - yaml, + yaml: str, sleep: int, - output, -): + output: str, +) -> None: yaml_workspace = tmp_path / "simple.yaml" yaml_workspace.write_text(yaml, encoding="utf-8") workspace = ConfigReader._from_file(yaml_workspace) @@ -1265,7 +1273,7 @@ def test_load_workspace_sleep( assert output in captured_pane -def test_first_pane_start_directory(session, tmp_path: pathlib.Path): +def test_first_pane_start_directory(session: Session, tmp_path: pathlib.Path) -> None: yaml_workspace = test_utils.get_workspace_file( "workspace/builder/first_pane_start_directory.yaml" ) @@ -1297,7 +1305,7 @@ def f(path: str, p: Pane) -> bool: @pytest.mark.skipif( has_lt_version("2.9"), reason="needs option introduced in tmux >= 2.9" ) -def test_layout_main_horizontal(session): +def test_layout_main_horizontal(session: Session) -> None: yaml_workspace = test_utils.get_workspace_file("workspace/builder/three_pane.yaml") workspace = ConfigReader._from_file(path=yaml_workspace) diff --git a/tests/workspace/test_config.py b/tests/workspace/test_config.py index 436175d4d79..e087fbeabf0 100644 --- a/tests/workspace/test_config.py +++ b/tests/workspace/test_config.py @@ -26,7 +26,9 @@ def load_workspace(path: t.Union[str, pathlib.Path]) -> t.Dict[str, t.Any]: ) -def test_export_json(tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData"): +def test_export_json( + tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData" +) -> None: json_workspace_file = tmp_path / "config.json" configparser = ConfigReader(config_fixture.sample_workspace.sample_workspace_dict) @@ -42,7 +44,7 @@ def test_export_json(tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData" # # There's no tests for this # -def test_find_workspace_file(tmp_path: pathlib.Path): +def test_find_workspace_file(tmp_path: pathlib.Path) -> None: configs = [str(tmp_path / x) for x in tmp_path.rglob("*.[json][ini][yaml]")] garbage_file = tmp_path / "config.psd" @@ -72,13 +74,13 @@ def test_find_workspace_file(tmp_path: pathlib.Path): assert len(configs) == files -def test_workspace_expand1(config_fixture: "WorkspaceTestData"): +def test_workspace_expand1(config_fixture: "WorkspaceTestData") -> None: """Expand shell commands from string to list.""" test_workspace = loader.expand(config_fixture.expand1.before_workspace) assert test_workspace == config_fixture.expand1.after_workspace() -def test_workspace_expand2(config_fixture: "WorkspaceTestData"): +def test_workspace_expand2(config_fixture: "WorkspaceTestData") -> None: """Expand shell commands from string to list.""" unexpanded_dict = ConfigReader._load( format="yaml", content=config_fixture.expand2.unexpanded_yaml() @@ -130,7 +132,7 @@ def test_workspace_expand2(config_fixture: "WorkspaceTestData"): } -def test_inheritance_workspace(): +def test_inheritance_workspace() -> None: workspace = inheritance_workspace_before # TODO: Look at verifying window_start_directory @@ -158,7 +160,7 @@ def test_inheritance_workspace(): assert workspace == inheritance_workspace_after -def test_shell_command_before(config_fixture: "WorkspaceTestData"): +def test_shell_command_before(config_fixture: "WorkspaceTestData") -> None: """Config inheritance for the nested 'start_command'.""" test_workspace = config_fixture.shell_command_before.config_unexpanded test_workspace = loader.expand(test_workspace) @@ -187,7 +189,7 @@ def test_trickle_relative_start_directory(config_fixture: "WorkspaceTestData") - assert test_workspace == config_fixture.trickle.expected -def test_trickle_window_with_no_pane_workspace(): +def test_trickle_window_with_no_pane_workspace() -> None: test_yaml = """ session_name: test_session windows: @@ -237,7 +239,7 @@ def test_expands_blank_panes(config_fixture: "WorkspaceTestData") -> None: assert loader.expand(test_workspace) == config_fixture.expand_blank.expected -def test_no_session_name(): +def test_no_session_name() -> None: yaml_workspace = """ - window_name: editor panes: @@ -258,7 +260,7 @@ def test_no_session_name(): assert excinfo.match(r'requires "session_name"') -def test_no_windows(): +def test_no_windows() -> None: yaml_workspace = """ session_name: test session """ @@ -270,7 +272,7 @@ def test_no_windows(): assert excinfo.match(r'list of "windows"') -def test_no_window_name(): +def test_no_window_name() -> None: yaml_workspace = """ session_name: test session windows: @@ -335,7 +337,7 @@ def test_replaces_env_variables(monkeypatch: pytest.MonkeyPatch) -> None: assert "logging @ %s" % env_val == sconfig["windows"][1]["window_name"] -def test_plugins(): +def test_plugins() -> None: yaml_workspace = """ session_name: test session plugins: tmuxp-plugin-one.plugin.TestPluginOne diff --git a/tests/workspace/test_finder.py b/tests/workspace/test_finder.py index cdb33e7323a..5535fd2b692 100644 --- a/tests/workspace/test_finder.py +++ b/tests/workspace/test_finder.py @@ -240,7 +240,7 @@ def test_find_workspace_file_arg( configdir: pathlib.Path, projectdir: pathlib.Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, + capsys: pytest.CaptureFixture[str], ) -> None: parser = argparse.ArgumentParser() parser.add_argument("workspace_file", type=str) diff --git a/tests/workspace/test_freezer.py b/tests/workspace/test_freezer.py index b6179f2897b..718ab13c4d8 100644 --- a/tests/workspace/test_freezer.py +++ b/tests/workspace/test_freezer.py @@ -3,6 +3,8 @@ import time import typing +from libtmux.session import Session + from tmuxp.config_reader import ConfigReader from tmuxp.workspace import freezer, validation from tmuxp.workspace.builder import WorkspaceBuilder @@ -13,7 +15,7 @@ from ..fixtures.structures import WorkspaceTestData -def test_freeze_config(session): +def test_freeze_config(session: Session) -> None: session_config = ConfigReader._from_file( test_utils.get_workspace_file("workspace/freezer/sample_workspace.yaml") ) @@ -76,14 +78,16 @@ def test_freeze_config(session): } -def test_inline_workspace(): +def test_inline_workspace() -> None: """:meth:`freezer.inline()` shell commands list to string.""" test_workspace = freezer.inline(ibefore_workspace) assert test_workspace == iafter_workspace -def test_export_yaml(tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData"): +def test_export_yaml( + tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData" +) -> None: yaml_workspace_file = tmp_path / "config.yaml" sample_workspace = freezer.inline( From b90789059994cdd2bef38c296874b5552919569b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 22 Sep 2023 19:24:38 -0500 Subject: [PATCH 04/39] More autogenerated types --- tests/test_util.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index 008af37ead7..3b79a01c710 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,5 +1,6 @@ """Tests for utility functions in tmux.""" import pytest +from libtmux.server import Server from tmuxp import exc from tmuxp.exc import BeforeLoadScriptError, BeforeLoadScriptNotExists @@ -8,7 +9,7 @@ from .constants import FIXTURE_PATH -def test_raise_BeforeLoadScriptNotExists_if_not_exists(): +def test_raise_BeforeLoadScriptNotExists_if_not_exists() -> None: script_file = FIXTURE_PATH / "script_noexists.sh" with pytest.raises(BeforeLoadScriptNotExists): @@ -18,14 +19,14 @@ def test_raise_BeforeLoadScriptNotExists_if_not_exists(): run_before_script(script_file) -def test_raise_BeforeLoadScriptError_if_retcode(): +def test_raise_BeforeLoadScriptError_if_retcode() -> None: script_file = FIXTURE_PATH / "script_failed.sh" with pytest.raises(BeforeLoadScriptError): run_before_script(script_file) -def test_return_stdout_if_ok(capsys: pytest.CaptureFixture[str]): +def test_return_stdout_if_ok(capsys: pytest.CaptureFixture[str]) -> None: script_file = FIXTURE_PATH / "script_complete.sh" run_before_script(script_file) @@ -33,7 +34,7 @@ def test_return_stdout_if_ok(capsys: pytest.CaptureFixture[str]): assert "hello" in out -def test_beforeload_returncode(): +def test_beforeload_returncode() -> None: script_file = FIXTURE_PATH / "script_failed.sh" with pytest.raises(exc.BeforeLoadScriptError) as excinfo: @@ -41,7 +42,7 @@ def test_beforeload_returncode(): assert excinfo.match(r"113") -def test_beforeload_returns_stderr_messages(): +def test_beforeload_returns_stderr_messages() -> None: script_file = FIXTURE_PATH / "script_failed.sh" with pytest.raises(exc.BeforeLoadScriptError) as excinfo: @@ -49,7 +50,9 @@ def test_beforeload_returns_stderr_messages(): assert excinfo.match(r"failed with returncode") -def test_get_session_should_default_to_local_attached_session(server, monkeypatch): +def test_get_session_should_default_to_local_attached_session( + server: Server, monkeypatch: pytest.MonkeyPatch +) -> None: server.new_session(session_name="myfirstsession") second_session = server.new_session(session_name="mysecondsession") @@ -60,7 +63,9 @@ def test_get_session_should_default_to_local_attached_session(server, monkeypatc assert get_session(server) == second_session -def test_get_session_should_return_first_session_if_no_active_session(server): +def test_get_session_should_return_first_session_if_no_active_session( + server: Server, +) -> None: first_session = server.new_session(session_name="myfirstsession") server.new_session(session_name="mysecondsession") From 27018ce4f949dcf25bce3f59c1ca85a8f3ef6d5a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 05:47:23 -0500 Subject: [PATCH 05/39] chore(mypy): Typings for test_plugin_helpers --- .../pluginsystem/partials/test_plugin_helpers.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py index 1f7bcdb7ee7..4220d084a27 100644 --- a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py +++ b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py @@ -1,10 +1,16 @@ -from typing import Iterable, Mapping, Union +import typing as t from tmuxp.plugin import TmuxpPlugin +class Config(t.TypedDict): + tmux_version: t.Optional[str] + tmuxp_version: t.Optional[str] + libtmux_version: t.Optional[str] + + class MyTestTmuxpPlugin(TmuxpPlugin): - def __init__(self, config: Mapping[str, Union[str, Iterable[str]]]) -> None: + def __init__(self, config: Config) -> None: assert isinstance(config, dict) tmux_version = config.pop("tmux_version", None) libtmux_version = config.pop("libtmux_version", None) From fa6e52060c9f2700a948661d00779f35e9f5edfb Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 05:47:32 -0500 Subject: [PATCH 06/39] chore(mypy): Typings for test_builder --- tests/workspace/test_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index cac1f091093..8dc64a0309d 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -963,7 +963,9 @@ def test_load_configs_separate_sessions(server: Server) -> None: assert len(server.sessions[1].windows) == 2 -def test_find_current_active_pane(server: Server, monkeypatch: pytest.MonkeyPatch) -> None: +def test_find_current_active_pane( + server: Server, monkeypatch: pytest.MonkeyPatch +) -> None: workspace = ConfigReader._from_file( path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) From 7b396020c4ce12b692b031ad20c1451bc9fae295 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 7 Jan 2023 11:33:34 -0600 Subject: [PATCH 07/39] chore(mypy): Typings for test_load --- tests/cli/test_load.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cli/test_load.py b/tests/cli/test_load.py index cfae28cf45e..9810e2107cd 100644 --- a/tests/cli/test_load.py +++ b/tests/cli/test_load.py @@ -450,9 +450,9 @@ def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: ], ) def test_load_plugins_version_fail_skip( - monkeypatch_plugin_test_packages, - cli_args, - inputs, + monkeypatch_plugin_test_packages: None, + cli_args: t.List[str], + inputs: t.List[str], capsys: pytest.CaptureFixture[str], ) -> None: with contextlib.suppress(SystemExit): From d4c7df5bf0877c62df790a518baa0fa751f54e31 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 22 Sep 2023 19:20:19 -0500 Subject: [PATCH 08/39] chore(mypy): Typings for test_finder --- tests/workspace/test_finder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/workspace/test_finder.py b/tests/workspace/test_finder.py index 5535fd2b692..a1e79a7d17f 100644 --- a/tests/workspace/test_finder.py +++ b/tests/workspace/test_finder.py @@ -257,7 +257,7 @@ def config_cmd(workspace_file: str) -> None: project_config = projectdir / ".tmuxp.yaml" - def check_cmd(config_arg) -> "_pytest.capture.CaptureResult": + def check_cmd(config_arg: str) -> "_pytest.capture.CaptureResult[str]": args = parser.parse_args([config_arg]) config_cmd(workspace_file=args.workspace_file) return capsys.readouterr() From a0598aa85adcda1c97ecd017ac0222b92e694803 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 05:36:26 -0500 Subject: [PATCH 09/39] chore(mypy): Typings for test_builder --- tests/workspace/test_builder.py | 37 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 8dc64a0309d..9cd384c733e 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -149,7 +149,7 @@ def f_check_again(): and CI. See https://github.com/tmux-python/tmuxp/issues/310. """.strip() ) -def test_suppress_history(session): +def test_suppress_history(session: Session) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/suppress_history.yaml") ) @@ -162,10 +162,10 @@ def test_suppress_history(session): inHistoryWindow = session.windows.get(window_name="inHistory") isMissingWindow = session.windows.get(window_name="isMissing") - def assertHistory(cmd, hist): + def assertHistory(cmd: str, hist: str) -> bool: return "inHistory" in cmd and cmd.endswith(hist) - def assertIsMissing(cmd, hist): + def assertIsMissing(cmd: str, hist: str) -> bool: return "isMissing" in cmd and not cmd.endswith(hist) for w, window_name, assertCase in [ @@ -303,8 +303,8 @@ def test_window_options_after(session: Session) -> None: builder = WorkspaceBuilder(session_config=workspace, server=session.server) builder.build(session=session) - def assert_last_line(p, s): - def f(): + def assert_last_line(p: Pane, s: str) -> bool: + def f() -> bool: pane_out = p.cmd("capture-pane", "-p", "-J").stdout while not pane_out[-1].strip(): # delete trailing lines tmux 1.8 pane_out.pop() @@ -400,7 +400,9 @@ def test_environment_variables(session: Session) -> None: has_gte_version("3.0"), reason="warnings are not needed for tmux >= 3.0", ) -def test_environment_variables_logs(session: Session, caplog: pytest.LogCaptureFixture): +def test_environment_variables_logs( + session: Session, caplog: pytest.LogCaptureFixture +) -> None: workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") ) @@ -468,7 +470,7 @@ def test_automatic_rename_option( assert w.name != "renamed_window" def check_window_name_mismatch() -> bool: - return w.name != portable_command + return bool(w.name != portable_command) assert retry_until(check_window_name_mismatch, 5, interval=0.25) @@ -670,7 +672,7 @@ def test_pane_order(session: Session) -> None: # at 0 since python list. pane_path = pane_paths[p_index - pane_base_index] - def f(pane_path: str, p: Pane): + def f(pane_path: str, p: Pane) -> bool: p.refresh() return p.pane_current_path == pane_path @@ -713,6 +715,7 @@ def test_before_load_throw_error_if_retcode_error(server: Server) -> None: with temp_session(server) as sess: session_name = sess.name + assert session_name is not None with pytest.raises(exc.BeforeLoadScriptError): builder.build(session=sess) @@ -736,7 +739,9 @@ def test_before_load_throw_error_if_file_not_exists(server: Server) -> None: with temp_session(server) as session: session_name = session.name - temp_session_exists = server.has_session(session.name) + + assert session_name is not None + temp_session_exists = server.has_session(session_name) assert temp_session_exists with pytest.raises((exc.BeforeLoadScriptNotExists, OSError)) as excinfo: builder.build(session=session) @@ -985,6 +990,8 @@ def test_find_current_active_pane( # Assign an active pane to the session second_session = server.sessions[1] first_pane_on_second_session_id = second_session.windows[0].panes[0].pane_id + + assert first_pane_on_second_session_id is not None monkeypatch.setenv("TMUX_PANE", first_pane_on_second_session_id) builder = WorkspaceBuilder(session_config=workspace, server=server) @@ -1134,7 +1141,7 @@ def test_load_workspace_enter( pane = session.attached_pane assert isinstance(pane, Pane) - def fn(): + def fn() -> bool: captured_pane = "\n".join(pane.capture_pane()) if should_see: @@ -1320,11 +1327,11 @@ def test_layout_main_horizontal(session: Session) -> None: assert len(window.panes) == 3 main_horizontal_pane, *panes = window.panes - def height(p): - return int(p.pane_height) + def height(p: Pane) -> int: + return int(p.pane_height) if p.pane_height is not None else 0 - def width(p): - return int(p.pane_width) + def width(p: Pane) -> int: + return int(p.pane_width) if p.pane_width is not None else 0 main_horizontal_pane_height = height(main_horizontal_pane) pane_heights = [height(pane) for pane in panes] @@ -1337,7 +1344,7 @@ def width(p): ), "The bottom row should be uniform height" assert width(main_horizontal_pane) > width(panes[0]) - def is_almost_equal(x, y): + def is_almost_equal(x: int, y: int) -> bool: return abs(x - y) <= 1 assert is_almost_equal(height(panes[0]), height(panes[1])) From 0523b9ae32b51b5a0490740a4a370a30a4f6750e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 05:44:56 -0500 Subject: [PATCH 10/39] chore(mypy): Typings for test_builder --- tests/workspace/test_builder.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 9cd384c733e..db31e419eed 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -97,7 +97,11 @@ def test_focus_pane_index(session: Session) -> None: pane_base_index = 0 if not pane_base_index else int(pane_base_index) # get the pane index for each pane - pane_base_indexes = [int(pane.index) for pane in session.attached_window.panes] + pane_base_indexes = [ + int(pane.index) + for pane in session.attached_window.panes + if pane is not None and pane.index is not None + ] pane_indexes_should_be = [pane_base_index + x for x in range(0, 3)] assert pane_indexes_should_be == pane_base_indexes @@ -129,7 +133,7 @@ def f_check() -> bool: p = None pane_path = "/" - def f_check_again(): + def f_check_again() -> bool: nonlocal p p = window3.attached_pane assert p is not None @@ -160,7 +164,9 @@ def test_suppress_history(session: Session) -> None: builder.build(session=session) inHistoryWindow = session.windows.get(window_name="inHistory") + assert inHistoryWindow is not None isMissingWindow = session.windows.get(window_name="isMissing") + assert isMissingWindow is not None def assertHistory(cmd: str, hist: str) -> bool: return "inHistory" in cmd and cmd.endswith(hist) @@ -175,6 +181,7 @@ def assertIsMissing(cmd: str, hist: str) -> bool: assert w.name == window_name w.select_window() p = w.attached_pane + assert p is not None p.select_pane() # Print the last-in-history command in the pane From 1482ca34f0f73fc71a0ed388c54daf429ee14371 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 05:55:03 -0500 Subject: [PATCH 11/39] chore(mypy): Typings for test_load.py --- tests/cli/test_load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cli/test_load.py b/tests/cli/test_load.py index 9810e2107cd..399cea5fe24 100644 --- a/tests/cli/test_load.py +++ b/tests/cli/test_load.py @@ -178,7 +178,7 @@ class CLILoadFixture(t.NamedTuple): expected_not_in_err: "ExpectedOutput" = None -TEST_LOAD_FIXTURES = [ +TEST_LOAD_FIXTURES: t.List[CLILoadFixture] = [ CLILoadFixture( test_id="dir-relative-dot-samedir", cli_args=["load", "."], From 16645f3af07910dee133f1f5763f0f73b180bf14 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 05:56:47 -0500 Subject: [PATCH 12/39] chore(mypy): Add typings for test_cli.py --- tests/cli/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index b8870be2e2b..f0e0687c4dd 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -102,7 +102,7 @@ def test_pass_config_dir_ClickPath( def config_cmd(workspace_file: str) -> None: tmuxp_echo(find_workspace_file(workspace_file, workspace_dir=configdir)) - def check_cmd(config_arg) -> "_pytest.capture.CaptureResult": + def check_cmd(config_arg: str) -> "_pytest.capture.CaptureResult[str]": args = parser.parse_args([config_arg]) config_cmd(workspace_file=args.workspace_file) return capsys.readouterr() From cc70fb6e5fdf1f20f41a469fc8c4da0fcff33123 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 05:57:15 -0500 Subject: [PATCH 13/39] chore(mypy): Add typings for test_util --- tests/test_util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_util.py b/tests/test_util.py index 3b79a01c710..dbb4f8b5e2c 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -58,6 +58,7 @@ def test_get_session_should_default_to_local_attached_session( # Assign an active pane to the session first_pane_on_second_session_id = second_session.windows[0].panes[0].pane_id + assert first_pane_on_second_session_id is not None monkeypatch.setenv("TMUX_PANE", first_pane_on_second_session_id) assert get_session(server) == second_session From 7bed027ce47c83982c65c5cb861201643ccb6468 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 06:01:19 -0500 Subject: [PATCH 14/39] chore(mypy): Basic typings for plugin test partials --- .../pluginsystem/partials/libtmux_version_fail.py | 12 ++++++------ .../pluginsystem/partials/tmux_version_fail.py | 12 ++++++------ .../pluginsystem/partials/tmuxp_version_fail.py | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py index 80d992f415b..16bb2f33313 100644 --- a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py @@ -2,8 +2,8 @@ class LibtmuxVersionFailMinPlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str] = { "plugin_name": "libtmux-min-version-fail", "libtmux_min_version": "0.8.3", "libtmux_version": "0.7.0", @@ -12,8 +12,8 @@ def __init__(self): class LibtmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str] = { "plugin_name": "libtmux-max-version-fail", "libtmux_max_version": "3.0", "libtmux_version": "3.5", @@ -22,8 +22,8 @@ def __init__(self): class LibtmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str | list[str]] = { "plugin_name": "libtmux-incompatible-version-fail", "libtmux_version_incompatible": ["0.7.1"], "libtmux_version": "0.7.1", diff --git a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py index 48b2f95f883..c2d208c4eaa 100644 --- a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py @@ -2,8 +2,8 @@ class TmuxVersionFailMinPlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str] = { "plugin_name": "tmux-min-version-fail", "tmux_min_version": "1.8", "tmux_version": "1.7", @@ -12,8 +12,8 @@ def __init__(self): class TmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str] = { "plugin_name": "tmux-max-version-fail", "tmux_max_version": "3.0", "tmux_version": "3.5", @@ -22,8 +22,8 @@ def __init__(self): class TmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str | list[str]] = { "plugin_name": "tmux-incompatible-version-fail", "tmux_version_incompatible": ["2.3"], "tmux_version": "2.3", diff --git a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py index 861f1cfcb82..d0c02ec5e7d 100644 --- a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py @@ -2,8 +2,8 @@ class TmuxpVersionFailMinPlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str] = { "plugin_name": "tmuxp-min-version-fail", "tmuxp_min_version": "1.7.0", "tmuxp_version": "1.6.3", @@ -12,8 +12,8 @@ def __init__(self): class TmuxpVersionFailMaxPlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str] = { "plugin_name": "tmuxp-max-version-fail", "tmuxp_max_version": "2.0.0", "tmuxp_version": "2.5", @@ -22,8 +22,8 @@ def __init__(self): class TmuxpVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: dict[str, str | list[str]] = { "plugin_name": "tmuxp-incompatible-version-fail", "tmuxp_version_incompatible": ["1.5.0"], "tmuxp_version": "1.5.0", From 9d00b771ff0be6f4846cc0ccd559c89ad341f0f6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 06:06:21 -0500 Subject: [PATCH 15/39] docs(conf): Typings for mypy --- docs/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index fea1c2e785f..f7c5dd4b7a0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,6 @@ if t.TYPE_CHECKING: from sphinx.application import Sphinx - # Get the project root dir, which is the parent dir of this cwd = pathlib.Path(__file__).parent project_root = cwd.parent @@ -177,7 +176,7 @@ aafig_default_options = {"scale": 0.75, "aspect": 0.5, "proportional": True} -def linkcode_resolve(domain, info): +def linkcode_resolve(domain: str, info: t.Dict[str, str]) -> t.Union[None, str]: """ Determine the URL corresponding to Python object @@ -210,7 +209,8 @@ def linkcode_resolve(domain, info): except AttributeError: pass else: - obj = unwrap(obj) + if callable(obj): + obj = unwrap(obj) try: fn = inspect.getsourcefile(obj) From 86cf7db752b68e9afb87b2a86fde078f5b82586e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 06:12:55 -0500 Subject: [PATCH 16/39] chore(mypy): Add typings for aafig --- docs/_ext/aafig.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/_ext/aafig.py b/docs/_ext/aafig.py index 3770613d68e..d3885405e2d 100644 --- a/docs/_ext/aafig.py +++ b/docs/_ext/aafig.py @@ -21,6 +21,10 @@ from sphinx.errors import SphinxError from sphinx.util.osutil import ensuredir, relative_uri +if t.TYPE_CHECKING: + from sphinx.application import Sphinx + + try: import aafigure except ImportError: @@ -32,14 +36,18 @@ DEFAULT_FORMATS = {"html": "svg", "latex": "pdf", "text": None} -def merge_dict(dst, src): +def merge_dict( + dst: t.Dict[str, t.Optional[str]], src: t.Dict[str, t.Optional[str]] +) -> t.Dict[str, t.Optional[str]]: for k, v in src.items(): if k not in dst: dst[k] = v return dst -def get_basename(text, options, prefix="aafig"): +def get_basename( + text: str, options: t.Dict[str, str], prefix: t.Optional[str] = "aafig" +) -> str: options = options.copy() if "format" in options: del options["format"] @@ -52,7 +60,7 @@ class AafigError(SphinxError): category = "aafig error" -class AafigDirective(images.Image): +class AafigDirective(images.Image): # type:ignore """ Directive to insert an ASCII art figure to be rendered by aafigure. """ @@ -71,7 +79,7 @@ class AafigDirective(images.Image): option_spec = images.Image.option_spec.copy() option_spec.update(own_option_spec) - def run(self): + def run(self) -> t.List[nodes.Node]: aafig_options = {} own_options_keys = [self.own_option_spec.keys(), "scale"] for k, v in self.options.items(): @@ -93,7 +101,7 @@ def run(self): return [image_node] -def render_aafig_images(app, doctree): +def render_aafig_images(app: "Sphinx", doctree: nodes.Node) -> None: format_map = app.builder.config.aafig_format merge_dict(format_map, DEFAULT_FORMATS) if aafigure is None: @@ -144,7 +152,9 @@ def __init__(self, *args: object, **kwargs: object) -> None: return super().__init__("aafigure module not installed", *args, **kwargs) -def render_aafigure(app, text, options): +def render_aafigure( + app: "Sphinx", text: str, options: t.Dict[str, str] +) -> t.Tuple[str, str, t.Optional[str], t.Optional[str]]: """ Render an ASCII art figure into the requested format output file. """ @@ -186,7 +196,7 @@ def render_aafigure(app, text, options): finally: if f is not None: f.close() - return relfn, outfn, id, extra + return relfn, outfn, None, extra except AafigError: pass @@ -204,10 +214,10 @@ def render_aafigure(app, text, options): with open(metadata_fname, "w") as f: f.write(extra) - return relfn, outfn, id, extra + return relfn, outfn, None, extra -def setup(app): +def setup(app: "Sphinx") -> None: app.add_directive("aafig", AafigDirective) app.connect("doctree-read", render_aafig_images) app.add_config_value("aafig_format", DEFAULT_FORMATS, "html") From 74a01fc728dacc4721271fbe7d08a913f0f92b35 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 07:03:18 -0500 Subject: [PATCH 17/39] feat(_types): TYPE_CHECKING-guarded types, add PluginConfigSchema --- src/tmuxp/_types.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/tmuxp/_types.py diff --git a/src/tmuxp/_types.py b/src/tmuxp/_types.py new file mode 100644 index 00000000000..79eac5c9c2a --- /dev/null +++ b/src/tmuxp/_types.py @@ -0,0 +1,27 @@ +"""Internal, :const:`typing.TYPE_CHECKING` guarded :term:`type annotations ` + +These are _not_ to be imported at runtime as `typing_extensions` is not +bundled with tmuxp. Usage example: + +>>> import typing as t + +>>> if t.TYPE_CHECKING: +... from tmuxp._types import PluginConfigSchema +... +""" +import typing as t + +from typing_extensions import NotRequired, TypedDict + + +class PluginConfigSchema(TypedDict): + plugin_name: NotRequired[str] + tmux_min_version: NotRequired[str] + tmux_max_version: NotRequired[str] + tmux_version_incompatible: NotRequired[t.List[str]] + libtmux_min_version: NotRequired[str] + libtmux_max_version: NotRequired[str] + libtmux_version_incompatible: NotRequired[t.List[str]] + tmuxp_min_version: NotRequired[str] + tmuxp_max_version: NotRequired[str] + tmuxp_version_incompatible: NotRequired[t.List[str]] From 74145bfebf3c8bd4e60f83ab3e643fce961507f0 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 06:27:32 -0500 Subject: [PATCH 18/39] refactor(plugin): Add typings --- src/tmuxp/plugin.py | 101 ++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/src/tmuxp/plugin.py b/src/tmuxp/plugin.py index 7d56316630c..b8c27a22b36 100644 --- a/src/tmuxp/plugin.py +++ b/src/tmuxp/plugin.py @@ -27,7 +27,11 @@ if t.TYPE_CHECKING: - from typing_extensions import TypedDict + from libtmux.session import Session + from libtmux.window import Window + from typing_extensions import TypedDict, Unpack + + from ._types import PluginConfigSchema class VersionConstraints(TypedDict): version: t.Union[Version, str] @@ -41,20 +45,52 @@ class TmuxpPluginVersionConstraints(TypedDict): libtmux: VersionConstraints +class Config(t.TypedDict): + plugin_name: str + tmux_min_version: str + tmux_max_version: t.Optional[str] + tmux_version_incompatible: t.Optional[t.List[str]] + libtmux_min_version: str + libtmux_max_version: t.Optional[str] + libtmux_version_incompatible: t.Optional[t.List[str]] + tmuxp_min_version: str + tmuxp_max_version: t.Optional[str] + tmuxp_version_incompatible: t.Optional[t.List[str]] + + +DEFAULT_CONFIG: "Config" = { + "plugin_name": "tmuxp-plugin", + "tmux_min_version": TMUX_MIN_VERSION, + "tmux_max_version": TMUX_MAX_VERSION, + "tmux_version_incompatible": None, + "libtmux_min_version": LIBTMUX_MIN_VERSION, + "libtmux_max_version": LIBTMUX_MAX_VERSION, + "libtmux_version_incompatible": None, + "tmuxp_min_version": TMUXP_MIN_VERSION, + "tmuxp_max_version": TMUXP_MAX_VERSION, + "tmuxp_version_incompatible": None, +} + + +def validate_plugin_config(config: "PluginConfigSchema") -> t.TypeGuard["Config"]: + return isinstance(config, dict) + + +def setup_plugin_config( + config: "PluginConfigSchema", default_config: "Config" = DEFAULT_CONFIG +) -> "Config": + new_config = config.copy() + for default_key, default_value in default_config.items(): + if default_key not in new_config: + new_config[default_key] = default_value + + assert validate_plugin_config(new_config) + + return new_config + + class TmuxpPlugin: - def __init__( - self, - plugin_name: str = "tmuxp-plugin", - tmux_min_version: str = TMUX_MIN_VERSION, - tmux_max_version: t.Optional[str] = TMUX_MAX_VERSION, - tmux_version_incompatible: t.Optional[t.List[str]] = None, - libtmux_min_version: str = LIBTMUX_MIN_VERSION, - libtmux_max_version: t.Optional[str] = LIBTMUX_MAX_VERSION, - libtmux_version_incompatible: t.Optional[t.List[str]] = None, - tmuxp_min_version: str = TMUXP_MIN_VERSION, - tmuxp_max_version: t.Optional[str] = TMUXP_MAX_VERSION, - tmuxp_version_incompatible: t.Optional[t.List[str]] = None, - ) -> None: + def __init__(self, **kwargs: "Unpack[PluginConfigSchema]") -> None: """ Initialize plugin. @@ -95,7 +131,8 @@ def __init__( Versions of tmuxp that are incompatible with the plugin """ - self.plugin_name = plugin_name + config = setup_plugin_config(config=kwargs) + self.plugin_name = config["plugin_name"] # Dependency versions self.tmux_version = get_version() @@ -105,26 +142,26 @@ def __init__( self.version_constraints: "TmuxpPluginVersionConstraints" = { "tmux": { "version": self.tmux_version, - "vmin": tmux_min_version, - "vmax": tmux_max_version, - "incompatible": tmux_version_incompatible - if tmux_version_incompatible + "vmin": config["tmux_min_version"], + "vmax": config["tmux_max_version"], + "incompatible": config["tmux_version_incompatible"] + if config["tmux_version_incompatible"] else [], }, "libtmux": { "version": self.libtmux_version, - "vmin": libtmux_min_version, - "vmax": libtmux_max_version, - "incompatible": libtmux_version_incompatible - if libtmux_version_incompatible + "vmin": config["libtmux_min_version"], + "vmax": config["libtmux_max_version"], + "incompatible": config["libtmux_version_incompatible"] + if config["libtmux_version_incompatible"] else [], }, "tmuxp": { "version": self.tmuxp_version, - "vmin": tmuxp_min_version, - "vmax": tmuxp_max_version, - "incompatible": tmuxp_version_incompatible - if tmuxp_version_incompatible + "vmin": config["tmuxp_min_version"], + "vmax": config["tmuxp_max_version"], + "incompatible": config["tmuxp_version_incompatible"] + if config["tmuxp_version_incompatible"] else [], }, } @@ -167,7 +204,7 @@ def _pass_version_check( return True - def before_workspace_builder(self, session): + def before_workspace_builder(self, session: "Session"): """ Provide a session hook previous to creating the workspace. @@ -180,7 +217,7 @@ def before_workspace_builder(self, session): session to hook into """ - def on_window_create(self, window): + def on_window_create(self, window: "Window"): """ Provide a window hook previous to doing anything with a window. @@ -192,7 +229,7 @@ def on_window_create(self, window): window to hook into """ - def after_window_finished(self, window): + def after_window_finished(self, window: "Window"): """ Provide a window hook after creating the window. @@ -206,7 +243,7 @@ def after_window_finished(self, window): window to hook into """ - def before_script(self, session): + def before_script(self, session: "Session"): """ Provide a session hook after the workspace has been built. @@ -234,7 +271,7 @@ def before_script(self, session): session to hook into """ - def reattach(self, session): + def reattach(self, session: "Session"): """ Provide a session hook before reattaching to the session. From 28d54c995acda3d80b159b88a11de065452913b1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 07:16:37 -0500 Subject: [PATCH 19/39] chore(mypy): Add typing for plugin tests --- .../fixtures/pluginsystem/partials/_types.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/fixtures/pluginsystem/partials/_types.py diff --git a/tests/fixtures/pluginsystem/partials/_types.py b/tests/fixtures/pluginsystem/partials/_types.py new file mode 100644 index 00000000000..ebe24eea04c --- /dev/null +++ b/tests/fixtures/pluginsystem/partials/_types.py @@ -0,0 +1,34 @@ +"""Internal, :const:`typing.TYPE_CHECKING` guarded :term:`type annotations ` + +These are _not_ to be imported at runtime as `typing_extensions` is not +bundled with tmuxp. Usage example: + +>>> import typing as t + +>>> if t.TYPE_CHECKING: +... from tmuxp.fixtures.pluginsystem.partials._types import PluginConfigSchema +... +""" +import typing as t + +from typing_extensions import NotRequired, TypedDict + + +class PluginTestConfigSchema(TypedDict): + """Same as PluginConfigSchema, but with tmux, libtmux, and tmuxp version""" + + tmux_version: NotRequired[str] + libtmux_version: NotRequired[str] + tmuxp_version: NotRequired[str] + + # Normal keys + plugin_name: NotRequired[str] + tmux_min_version: NotRequired[str] + tmux_max_version: NotRequired[str] + tmux_version_incompatible: NotRequired[t.List[str]] + libtmux_min_version: NotRequired[str] + libtmux_max_version: NotRequired[str] + libtmux_version_incompatible: NotRequired[t.List[str]] + tmuxp_min_version: NotRequired[str] + tmuxp_max_version: NotRequired[str] + tmuxp_version_incompatible: NotRequired[t.List[str]] From ba778314618dbd1d82d1c88e1663c76fcb95dee7 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 07:16:54 -0500 Subject: [PATCH 20/39] chore(mypy): Typings for plugin tests --- tests/fixtures/pluginsystem/partials/all_pass.py | 7 ++++++- .../pluginsystem/partials/libtmux_version_fail.py | 11 ++++++++--- .../pluginsystem/partials/test_plugin_helpers.py | 11 ++++++----- .../pluginsystem/partials/tmux_version_fail.py | 11 ++++++++--- .../pluginsystem/partials/tmuxp_version_fail.py | 11 ++++++++--- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/tests/fixtures/pluginsystem/partials/all_pass.py b/tests/fixtures/pluginsystem/partials/all_pass.py index 8fea533d2f1..af1b238323c 100644 --- a/tests/fixtures/pluginsystem/partials/all_pass.py +++ b/tests/fixtures/pluginsystem/partials/all_pass.py @@ -1,9 +1,14 @@ +import typing as t + from .test_plugin_helpers import MyTestTmuxpPlugin +if t.TYPE_CHECKING: + from ._types import PluginTestConfigSchema + class AllVersionPassPlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config = { + config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-plugin-my-tmuxp-plugin", "tmux_min_version": "1.8", "tmux_max_version": "100.0", diff --git a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py index 16bb2f33313..bcd75716d09 100644 --- a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py @@ -1,9 +1,14 @@ +import typing as t + from .test_plugin_helpers import MyTestTmuxpPlugin +if t.TYPE_CHECKING: + from ._types import PluginTestConfigSchema + class LibtmuxVersionFailMinPlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str] = { + config: "PluginTestConfigSchema" = { "plugin_name": "libtmux-min-version-fail", "libtmux_min_version": "0.8.3", "libtmux_version": "0.7.0", @@ -13,7 +18,7 @@ def __init__(self) -> None: class LibtmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str] = { + config: "PluginTestConfigSchema" = { "plugin_name": "libtmux-max-version-fail", "libtmux_max_version": "3.0", "libtmux_version": "3.5", @@ -23,7 +28,7 @@ def __init__(self) -> None: class LibtmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str | list[str]] = { + config: "PluginTestConfigSchema" = { "plugin_name": "libtmux-incompatible-version-fail", "libtmux_version_incompatible": ["0.7.1"], "libtmux_version": "0.7.1", diff --git a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py index 4220d084a27..66434259ffa 100644 --- a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py +++ b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py @@ -2,20 +2,21 @@ from tmuxp.plugin import TmuxpPlugin +if t.TYPE_CHECKING: + from tmuxp._types import PluginConfigSchema -class Config(t.TypedDict): - tmux_version: t.Optional[str] - tmuxp_version: t.Optional[str] - libtmux_version: t.Optional[str] + from ._types import PluginTestConfigSchema class MyTestTmuxpPlugin(TmuxpPlugin): - def __init__(self, config: Config) -> None: + def __init__(self, config: "PluginTestConfigSchema") -> None: assert isinstance(config, dict) tmux_version = config.pop("tmux_version", None) libtmux_version = config.pop("libtmux_version", None) tmuxp_version = config.pop("tmuxp_version", None) + t.cast("PluginConfigSchema", config) + TmuxpPlugin.__init__(self, **config) # WARNING! This should not be done in anything but a test diff --git a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py index c2d208c4eaa..fb88dc8f0c8 100644 --- a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py @@ -1,9 +1,14 @@ +import typing as t + from .test_plugin_helpers import MyTestTmuxpPlugin +if t.TYPE_CHECKING: + from ._types import PluginTestConfigSchema + class TmuxVersionFailMinPlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str] = { + config: "PluginTestConfigSchema" = { "plugin_name": "tmux-min-version-fail", "tmux_min_version": "1.8", "tmux_version": "1.7", @@ -13,7 +18,7 @@ def __init__(self) -> None: class TmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str] = { + config: "PluginTestConfigSchema" = { "plugin_name": "tmux-max-version-fail", "tmux_max_version": "3.0", "tmux_version": "3.5", @@ -23,7 +28,7 @@ def __init__(self) -> None: class TmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str | list[str]] = { + config: "PluginTestConfigSchema" = { "plugin_name": "tmux-incompatible-version-fail", "tmux_version_incompatible": ["2.3"], "tmux_version": "2.3", diff --git a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py index d0c02ec5e7d..945901368f3 100644 --- a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py @@ -1,9 +1,14 @@ +import typing as t + from .test_plugin_helpers import MyTestTmuxpPlugin +if t.TYPE_CHECKING: + from ._types import PluginTestConfigSchema + class TmuxpVersionFailMinPlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str] = { + config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-min-version-fail", "tmuxp_min_version": "1.7.0", "tmuxp_version": "1.6.3", @@ -13,7 +18,7 @@ def __init__(self) -> None: class TmuxpVersionFailMaxPlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str] = { + config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-max-version-fail", "tmuxp_max_version": "2.0.0", "tmuxp_version": "2.5", @@ -23,7 +28,7 @@ def __init__(self) -> None: class TmuxpVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): def __init__(self) -> None: - config: dict[str, str | list[str]] = { + config: "PluginTestConfigSchema" = { "plugin_name": "tmuxp-incompatible-version-fail", "tmuxp_version_incompatible": ["1.5.0"], "tmuxp_version": "1.5.0", From b7749b3487f84485bdd372bbaec71c2b592e6d9c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:09:05 -0500 Subject: [PATCH 21/39] refactor(tests[pluginsystem]): Use double splat --- tests/fixtures/pluginsystem/partials/all_pass.py | 2 +- .../pluginsystem/partials/libtmux_version_fail.py | 6 +++--- .../fixtures/pluginsystem/partials/test_plugin_helpers.py | 8 ++++++-- tests/fixtures/pluginsystem/partials/tmux_version_fail.py | 6 +++--- .../fixtures/pluginsystem/partials/tmuxp_version_fail.py | 6 +++--- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/fixtures/pluginsystem/partials/all_pass.py b/tests/fixtures/pluginsystem/partials/all_pass.py index af1b238323c..393fa00c429 100644 --- a/tests/fixtures/pluginsystem/partials/all_pass.py +++ b/tests/fixtures/pluginsystem/partials/all_pass.py @@ -22,4 +22,4 @@ def __init__(self) -> None: "tmux_version": "3.0", "tmuxp_version": "1.7.0", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) diff --git a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py index bcd75716d09..f801c21391a 100644 --- a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py @@ -13,7 +13,7 @@ def __init__(self) -> None: "libtmux_min_version": "0.8.3", "libtmux_version": "0.7.0", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) class LibtmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): @@ -23,7 +23,7 @@ def __init__(self) -> None: "libtmux_max_version": "3.0", "libtmux_version": "3.5", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) class LibtmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): @@ -33,4 +33,4 @@ def __init__(self) -> None: "libtmux_version_incompatible": ["0.7.1"], "libtmux_version": "0.7.1", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) diff --git a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py index 66434259ffa..63d3b06715c 100644 --- a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py +++ b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py @@ -3,13 +3,15 @@ from tmuxp.plugin import TmuxpPlugin if t.TYPE_CHECKING: + from typing_extensions import Unpack + from tmuxp._types import PluginConfigSchema from ._types import PluginTestConfigSchema class MyTestTmuxpPlugin(TmuxpPlugin): - def __init__(self, config: "PluginTestConfigSchema") -> None: + def __init__(self, **config: "Unpack[PluginTestConfigSchema]") -> None: assert isinstance(config, dict) tmux_version = config.pop("tmux_version", None) libtmux_version = config.pop("libtmux_version", None) @@ -17,7 +19,9 @@ def __init__(self, config: "PluginTestConfigSchema") -> None: t.cast("PluginConfigSchema", config) - TmuxpPlugin.__init__(self, **config) + assert "tmux_version" not in config + + super().__init__(**config) # WARNING! This should not be done in anything but a test if tmux_version: diff --git a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py index fb88dc8f0c8..acf886d588c 100644 --- a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py @@ -13,7 +13,7 @@ def __init__(self) -> None: "tmux_min_version": "1.8", "tmux_version": "1.7", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) class TmuxVersionFailMaxPlugin(MyTestTmuxpPlugin): @@ -23,7 +23,7 @@ def __init__(self) -> None: "tmux_max_version": "3.0", "tmux_version": "3.5", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) class TmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): @@ -34,4 +34,4 @@ def __init__(self) -> None: "tmux_version": "2.3", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) diff --git a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py index 945901368f3..702d843e7a0 100644 --- a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py @@ -13,7 +13,7 @@ def __init__(self) -> None: "tmuxp_min_version": "1.7.0", "tmuxp_version": "1.6.3", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) class TmuxpVersionFailMaxPlugin(MyTestTmuxpPlugin): @@ -23,7 +23,7 @@ def __init__(self) -> None: "tmuxp_max_version": "2.0.0", "tmuxp_version": "2.5", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) class TmuxpVersionFailIncompatiblePlugin(MyTestTmuxpPlugin): @@ -33,4 +33,4 @@ def __init__(self) -> None: "tmuxp_version_incompatible": ["1.5.0"], "tmuxp_version": "1.5.0", } - MyTestTmuxpPlugin.__init__(self, config) + MyTestTmuxpPlugin.__init__(self, **config) From 1d310ed257bcf211be54c76c91a5c7fee1ca4693 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:09:32 -0500 Subject: [PATCH 22/39] chore(mypy): Typings for tests/fixtures/pluginsystem/plugins --- .../tmuxp_test_plugin_awf/plugin.py | 11 ++++++++--- .../tmuxp_test_plugin_bs/plugin.py | 11 ++++++++--- .../tmuxp_test_plugin_bwb/plugin.py | 11 ++++++++--- .../tmuxp_test_plugin_fail/plugin.py | 9 +++++++-- .../tmuxp_test_plugin_owc/plugin.py | 11 ++++++++--- .../tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py | 11 ++++++++--- 6 files changed, 47 insertions(+), 17 deletions(-) diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py index fb3ddc35727..b4305ca7e7f 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py @@ -1,11 +1,16 @@ +import typing as t + from tmuxp.plugin import TmuxpPlugin +if t.TYPE_CHECKING: + from libtmux.window import Window + class PluginAfterWindowFinished(TmuxpPlugin): - def __init__(self): - self.message = "[+] This is the Tmuxp Test Plugin" + def __init__(self) -> None: + self.message: str = "[+] This is the Tmuxp Test Plugin" - def after_window_finished(self, window): + def after_window_finished(self, window: "Window") -> None: if window.name == "editor": window.rename_window("plugin_test_awf") elif window.name == "awf_mw_test": diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py index faacc0cd862..e2fbf98aca9 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py @@ -1,9 +1,14 @@ +import typing as t + from tmuxp.plugin import TmuxpPlugin +if t.TYPE_CHECKING: + from libtmux.session import Session + class PluginBeforeScript(TmuxpPlugin): - def __init__(self): - self.message = "[+] This is the Tmuxp Test Plugin" + def __init__(self) -> None: + self.message: str = "[+] This is the Tmuxp Test Plugin" - def before_script(self, session): + def before_script(self, session: "Session") -> None: session.rename_session("plugin_test_bs") diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py index aa9cb1419e6..d1be0b37723 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py @@ -1,9 +1,14 @@ +import typing as t + from tmuxp.plugin import TmuxpPlugin +if t.TYPE_CHECKING: + from libtmux.session import Session + class PluginBeforeWorkspaceBuilder(TmuxpPlugin): - def __init__(self): - self.message = "[+] This is the Tmuxp Test Plugin" + def __init__(self) -> None: + self.message: str = "[+] This is the Tmuxp Test Plugin" - def before_workspace_builder(self, session): + def before_workspace_builder(self, session: "Session") -> None: session.rename_session("plugin_test_bwb") diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py index 4de06ee2e72..5534c5afedf 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py @@ -1,9 +1,14 @@ +import typing as t + from tmuxp.plugin import TmuxpPlugin +if t.TYPE_CHECKING: + from tmuxp._types import PluginConfigSchema + class PluginFailVersion(TmuxpPlugin): - def __init__(self): - config = { + def __init__(self) -> None: + config: "PluginConfigSchema" = { "plugin_name": "tmuxp-plugin-fail-version", "tmuxp_max_version": "0.0.0", } diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py index 0c6af1dc698..5f900a494f6 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py @@ -1,11 +1,16 @@ +import typing as t + from tmuxp.plugin import TmuxpPlugin +if t.TYPE_CHECKING: + from libtmux.window import Window + class PluginOnWindowCreate(TmuxpPlugin): - def __init__(self): - self.message = "[+] This is the Tmuxp Test Plugin" + def __init__(self) -> None: + self.message: str = "[+] This is the Tmuxp Test Plugin" - def on_window_create(self, window): + def on_window_create(self, window: "Window") -> None: if window.name == "editor": window.rename_window("plugin_test_owc") elif window.name == "owc_mw_test": diff --git a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py index 1cc6ccb8f03..25a43e93f96 100644 --- a/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py +++ b/tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py @@ -1,9 +1,14 @@ +import typing as t + from tmuxp.plugin import TmuxpPlugin +if t.TYPE_CHECKING: + from libtmux.session import Session + class PluginReattach(TmuxpPlugin): - def __init__(self): - self.message = "[+] This is the Tmuxp Test Plugin" + def __init__(self) -> None: + self.message: str = "[+] This is the Tmuxp Test Plugin" - def reattach(self, session): + def reattach(self, session: "Session") -> None: session.rename_session("plugin_test_r") From 7415da4ef45b3c0bad2f88f7c444690b40a2460c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:09:46 -0500 Subject: [PATCH 23/39] chore(mypy): Typings for cli/edit --- src/tmuxp/cli/edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tmuxp/cli/edit.py b/src/tmuxp/cli/edit.py index 184077c3cc7..31b263dd362 100644 --- a/src/tmuxp/cli/edit.py +++ b/src/tmuxp/cli/edit.py @@ -22,7 +22,7 @@ def create_edit_subparser( def command_edit( workspace_file: t.Union[str, pathlib.Path], parser: t.Optional[argparse.ArgumentParser] = None, -): +) -> None: workspace_file = find_workspace_file(workspace_file) sys_editor = os.environ.get("EDITOR", "vim") From 7d6be7e59f3e3517e08c362ee1621f333890618d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:13:22 -0500 Subject: [PATCH 24/39] chore(mypy): Ignore odd type issue in test_plugin_helpers tests/fixtures/pluginsystem/partials/test_plugin_helpers.py:24: error: Extra argument "tmux_version" from **args for "__init__" of "TmuxpPlugin" [misc] --- tests/fixtures/pluginsystem/partials/test_plugin_helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py index 63d3b06715c..8b6c054e29b 100644 --- a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py +++ b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py @@ -21,7 +21,9 @@ def __init__(self, **config: "Unpack[PluginTestConfigSchema]") -> None: assert "tmux_version" not in config - super().__init__(**config) + # tests/fixtures/pluginsystem/partials/test_plugin_helpers.py:24: error: Extra + # argument "tmux_version" from **args for "__init__" of "TmuxpPlugin" [misc] + super().__init__(**config) # type:ignore # WARNING! This should not be done in anything but a test if tmux_version: From 83d0aa9d9358a739a978fc64330d6315de41e2e7 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:22:35 -0500 Subject: [PATCH 25/39] chore(mypy): Add typings for cli/import_config --- src/tmuxp/cli/import_config.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index 301f0dd8cfb..9048331fa9c 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -59,7 +59,7 @@ def command_import( workspace_file: str, print_list: str, parser: argparse.ArgumentParser, -): +) -> None: """Import a teamocil/tmuxinator config.""" @@ -116,9 +116,14 @@ def create_import_subparser( return parser +class ImportConfigFn(t.Protocol): + def __call__(self, workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + ... + + def import_config( workspace_file: str, - importfunc: t.Callable, + importfunc: ImportConfigFn, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: existing_workspace_file = ConfigReader._from_file(pathlib.Path(workspace_file)) From 729ae23ccad5dca67f9e1630d5b7468f6f5fb4d4 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:23:48 -0500 Subject: [PATCH 26/39] chore(cli[freeze]): Remove session_completion --- src/tmuxp/cli/freeze.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tmuxp/cli/freeze.py b/src/tmuxp/cli/freeze.py index 3a8f0502155..af82e9cbf4e 100644 --- a/src/tmuxp/cli/freeze.py +++ b/src/tmuxp/cli/freeze.py @@ -31,12 +31,6 @@ class CLIFreezeNamespace(argparse.Namespace): force: t.Optional[bool] -def session_completion(ctx, params, incomplete): - server = Server() - choices = [session.name for session in server.sessions] - return sorted(str(c) for c in choices if str(c).startswith(incomplete)) - - def create_freeze_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: From 8df59398418cd9d8d58280fab16352c32de369d6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:24:10 -0500 Subject: [PATCH 27/39] chore(mypy): Add typings for cli/load --- src/tmuxp/cli/load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index 2873d764f7c..e965299f90f 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -153,7 +153,7 @@ def load_plugins(session_config: t.Dict[str, t.Any]) -> t.List[t.Any]: return plugins -def _reattach(builder: WorkspaceBuilder): +def _reattach(builder: WorkspaceBuilder) -> None: """ Reattach session (depending on env being inside tmux already or not) From df84c46b6aaae8316165e18bcad1b7a6660e9920 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 08:29:41 -0500 Subject: [PATCH 28/39] chore(mypy): Typings for cli/debug_info --- src/tmuxp/cli/debug_info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tmuxp/cli/debug_info.py b/src/tmuxp/cli/debug_info.py index d72e3fc1ac6..df6a65e19dc 100644 --- a/src/tmuxp/cli/debug_info.py +++ b/src/tmuxp/cli/debug_info.py @@ -29,19 +29,19 @@ def command_debug_info( Print debug info to submit with Issues. """ - def prepend_tab(strings): + def prepend_tab(strings: t.List[str]) -> t.List[str]: """ Prepend tab to strings in list. """ return ["\t%s" % x for x in strings] - def output_break(): + def output_break() -> str: """ Generate output break. """ return "-" * 25 - def format_tmux_resp(std_resp): + def format_tmux_resp(std_resp: tmux_cmd) -> str: """ Format tmux command response for tmuxp stdout. """ From 68b562829908d4f73a3f8bef59c95616684efccb Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 09:15:27 -0500 Subject: [PATCH 29/39] refactor(mypy): Add typings for cli/utils --- src/tmuxp/cli/utils.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/tmuxp/cli/utils.py b/src/tmuxp/cli/utils.py index 91f2ef165c5..482f20d0421 100644 --- a/src/tmuxp/cli/utils.py +++ b/src/tmuxp/cli/utils.py @@ -34,9 +34,9 @@ def tmuxp_echo( def prompt( name: str, - default: t.Any = None, + default: t.Optional[str] = None, value_proc: t.Optional[t.Callable[[str], str]] = None, -) -> t.Any: +) -> str: """Return user input from command line. :meth:`~prompt`, :meth:`~prompt_bool` and :meth:`prompt_choices` are from `flask-script`_. See the `flask-script license`_. @@ -107,15 +107,12 @@ def prompt_yes_no(name: str, default: bool = True) -> bool: return prompt_bool(name, default=default) -_C = t.TypeVar("_C") - - def prompt_choices( name: str, - choices: t.Union[t.List[_C], t.Tuple[str, _C]], - default: t.Optional[_C] = None, + choices: t.Union[t.List[str], t.Tuple[str, str]], + default: t.Optional[str] = None, no_choice: t.Sequence[str] = ("none",), -) -> t.Optional[_C]: +) -> t.Optional[str]: """Return user input from command line from set of provided choices. :param name: prompt text :param choices: list or tuple of available choices. Choices may be @@ -125,8 +122,8 @@ def prompt_choices( :rtype: str """ - _choices = [] - options = [] + _choices: t.List[str] = [] + options: t.List[str] = [] for choice in choices: if isinstance(choice, str): From c77277e75301cab7477837d546a9a1636635e12f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 09:20:30 -0500 Subject: [PATCH 30/39] chore(mypy): Add typings for cli/freeze --- src/tmuxp/cli/freeze.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tmuxp/cli/freeze.py b/src/tmuxp/cli/freeze.py index af82e9cbf4e..88a2f9751bf 100644 --- a/src/tmuxp/cli/freeze.py +++ b/src/tmuxp/cli/freeze.py @@ -171,12 +171,14 @@ def extract_workspace_format( workspace_format = extract_workspace_format(dest) if not is_valid_ext(workspace_format): - workspace_format = prompt_choices( + _workspace_format = prompt_choices( "Couldn't ascertain one of [%s] from file name. Convert to" % ", ".join(valid_workspace_formats), - choices=valid_workspace_formats, + choices=t.cast(t.List[str], valid_workspace_formats), default="yaml", ) + assert is_valid_ext(_workspace_format) + workspace_format = _workspace_format if workspace_format == "yaml": workspace = configparser.dump( From c9655771b2850a15748210a0e5311434c1b3779f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 09:26:41 -0500 Subject: [PATCH 31/39] chore(mypy): Fix typings for workspace/freezer --- src/tmuxp/workspace/freezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tmuxp/workspace/freezer.py b/src/tmuxp/workspace/freezer.py index 8df2e0ee2b5..0d1d14e98fb 100644 --- a/src/tmuxp/workspace/freezer.py +++ b/src/tmuxp/workspace/freezer.py @@ -28,7 +28,7 @@ def inline(workspace_dict: t.Dict[str, t.Any]) -> t.Any: workspace_dict["shell_command"] = workspace_dict["shell_command"][0] if len(workspace_dict.keys()) == 1: - workspace_dict = workspace_dict["shell_command"] + return workspace_dict["shell_command"] if ( "shell_command_before" in workspace_dict and isinstance(workspace_dict["shell_command_before"], list) From 1496f1da85247cae331e493dfe203fb1b218f4f9 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 09:49:45 -0500 Subject: [PATCH 32/39] refactor(mypy): Typings for tmuxp/shell --- src/tmuxp/shell.py | 68 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/src/tmuxp/shell.py b/src/tmuxp/shell.py index 1955d2bc325..bb06fb4b641 100644 --- a/src/tmuxp/shell.py +++ b/src/tmuxp/shell.py @@ -12,12 +12,35 @@ logger = logging.getLogger(__name__) if t.TYPE_CHECKING: - from typing_extensions import TypeAlias + from types import ModuleType + + from libtmux.pane import Pane + from libtmux.server import Server + from libtmux.session import Session + from libtmux.window import Window + from typing_extensions import NotRequired, TypeAlias, TypedDict, Unpack CLIShellLiteral: TypeAlias = t.Literal[ "best", "pdb", "code", "ptipython", "ptpython", "ipython", "bpython" ] + class LaunchOptionalImports(TypedDict): + server: NotRequired["Server"] + session: NotRequired["Session"] + window: NotRequired["Window"] + pane: NotRequired["Pane"] + + class LaunchImports(t.TypedDict): + libtmux: ModuleType + Server: t.Type[Server] + Session: t.Type[Session] + Window: t.Type[Window] + Pane: t.Type[Pane] + server: t.Optional["Server"] + session: t.Optional["Session"] + window: t.Optional["Window"] + pane: t.Optional["Pane"] + def has_ipython() -> bool: try: @@ -77,13 +100,15 @@ def detect_best_shell() -> "CLIShellLiteral": return "code" -def get_bpython(options, extra_args=None): +def get_bpython( + options: "LaunchOptionalImports", extra_args: t.Optional[t.Dict[str, t.Any]] = None +) -> t.Callable[[], None]: if extra_args is None: extra_args = {} from bpython import embed # F841 - def launch_bpython(): + def launch_bpython() -> None: imported_objects = get_launch_args(**options) kwargs = {} if extra_args: @@ -93,16 +118,18 @@ def launch_bpython(): return launch_bpython -def get_ipython_arguments(): +def get_ipython_arguments() -> t.List[str]: ipython_args = "IPYTHON_ARGUMENTS" return os.environ.get(ipython_args, "").split() -def get_ipython(options, **extra_args): +def get_ipython( + options: "LaunchOptionalImports", **extra_args: t.Dict[str, t.Any] +) -> t.Any: try: from IPython import start_ipython - def launch_ipython(): + def launch_ipython() -> None: imported_objects = get_launch_args(**options) ipython_arguments = extra_args or get_ipython_arguments() start_ipython(argv=ipython_arguments, user_ns=imported_objects) @@ -115,7 +142,7 @@ def launch_ipython(): # Notebook not supported for IPython < 0.11. from IPython.Shell import IPShell - def launch_ipython(): + def launch_ipython() -> None: imported_objects = get_launch_args(**options) shell = IPShell(argv=[], user_ns=imported_objects) shell.mainloop() @@ -123,13 +150,13 @@ def launch_ipython(): return launch_ipython -def get_ptpython(options, vi_mode=False): +def get_ptpython(options: "LaunchOptionalImports", vi_mode: bool = False) -> t.Any: try: from ptpython.repl import embed, run_config except ImportError: from prompt_toolkit.contrib.repl import embed, run_config - def launch_ptpython(): + def launch_ptpython() -> None: imported_objects = get_launch_args(**options) history_filename = str(pathlib.Path("~/.ptpython_history").expanduser()) embed( @@ -142,7 +169,7 @@ def launch_ptpython(): return launch_ptpython -def get_ptipython(options, vi_mode=False): +def get_ptipython(options: "LaunchOptionalImports", vi_mode: bool = False) -> t.Any: """Based on django-extensions Run renamed to launch, get_imported_objects renamed to get_launch_args @@ -155,7 +182,7 @@ def get_ptipython(options, vi_mode=False): from prompt_toolkit.contrib.ipython import embed from prompt_toolkit.contrib.repl import run_config - def launch_ptipython(): + def launch_ptipython() -> None: imported_objects = get_launch_args(**options) history_filename = str(pathlib.Path("~/.ptpython_history").expanduser()) embed( @@ -168,7 +195,7 @@ def launch_ptipython(): return launch_ptipython -def get_launch_args(**kwargs): +def get_launch_args(**kwargs: "Unpack[LaunchOptionalImports]") -> "LaunchImports": import libtmux from libtmux.pane import Pane from libtmux.server import Server @@ -188,7 +215,7 @@ def get_launch_args(**kwargs): } -def get_code(use_pythonrc, imported_objects): +def get_code(use_pythonrc: bool, imported_objects: "LaunchImports") -> t.Any: import code try: @@ -201,7 +228,11 @@ def get_code(use_pythonrc, imported_objects): # we already know 'readline' was imported successfully. import rlcompleter - readline.set_completer(rlcompleter.Completer(imported_objects).complete) + readline.set_completer( + rlcompleter.Completer( + imported_objects, # type:ignore + ).complete + ) # Enable tab completion on systems using libedit (e.g. macOS). # These lines are copied from Lib/site.py on Python 3.4. readline_doc = getattr(readline, "__doc__", "") @@ -226,9 +257,12 @@ def get_code(use_pythonrc, imported_objects): pythonrc_code = handle.read() # Match the behavior of the cpython shell where an error in # PYTHONSTARTUP prints an exception and continues. - exec(compile(pythonrc_code, pythonrc, "exec"), imported_objects) + exec( + compile(pythonrc_code, pythonrc, "exec"), + imported_objects, # type:ignore + ) - def launch_code(): + def launch_code() -> None: code.interact(local=imported_objects) return launch_code @@ -238,7 +272,7 @@ def launch( shell: t.Optional["CLIShellLiteral"] = "best", use_pythonrc: bool = False, use_vi_mode: bool = False, - **kwargs + **kwargs: "Unpack[LaunchOptionalImports]" ) -> None: # Also allowing passing shell='code' to force using code.interact imported_objects = get_launch_args(**kwargs) From 954f36f992d020719a1170b61c8fd8a52bf7f708 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 10:21:15 -0500 Subject: [PATCH 33/39] chore(mypy): Add typings for plugin --- src/tmuxp/plugin.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tmuxp/plugin.py b/src/tmuxp/plugin.py index b8c27a22b36..f6b49432d22 100644 --- a/src/tmuxp/plugin.py +++ b/src/tmuxp/plugin.py @@ -29,7 +29,7 @@ if t.TYPE_CHECKING: from libtmux.session import Session from libtmux.window import Window - from typing_extensions import TypedDict, Unpack + from typing_extensions import TypedDict, TypeGuard, Unpack from ._types import PluginConfigSchema @@ -72,7 +72,7 @@ class Config(t.TypedDict): } -def validate_plugin_config(config: "PluginConfigSchema") -> t.TypeGuard["Config"]: +def validate_plugin_config(config: "PluginConfigSchema") -> "TypeGuard[Config]": return isinstance(config, dict) @@ -82,7 +82,7 @@ def setup_plugin_config( new_config = config.copy() for default_key, default_value in default_config.items(): if default_key not in new_config: - new_config[default_key] = default_value + new_config[default_key] = default_value # type:ignore assert validate_plugin_config(new_config) @@ -204,7 +204,7 @@ def _pass_version_check( return True - def before_workspace_builder(self, session: "Session"): + def before_workspace_builder(self, session: "Session") -> None: """ Provide a session hook previous to creating the workspace. @@ -217,7 +217,7 @@ def before_workspace_builder(self, session: "Session"): session to hook into """ - def on_window_create(self, window: "Window"): + def on_window_create(self, window: "Window") -> None: """ Provide a window hook previous to doing anything with a window. @@ -229,7 +229,7 @@ def on_window_create(self, window: "Window"): window to hook into """ - def after_window_finished(self, window: "Window"): + def after_window_finished(self, window: "Window") -> None: """ Provide a window hook after creating the window. @@ -243,7 +243,7 @@ def after_window_finished(self, window: "Window"): window to hook into """ - def before_script(self, session: "Session"): + def before_script(self, session: "Session") -> None: """ Provide a session hook after the workspace has been built. @@ -271,7 +271,7 @@ def before_script(self, session: "Session"): session to hook into """ - def reattach(self, session: "Session"): + def reattach(self, session: "Session") -> None: """ Provide a session hook before reattaching to the session. From 083f5f87fd09a956c0d7516136f541aaaf86f3eb Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 10:25:27 -0500 Subject: [PATCH 34/39] chore(mypy): Typings for config_reader --- src/tmuxp/config_reader.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/tmuxp/config_reader.py b/src/tmuxp/config_reader.py index 3217cbc4422..6c0913d2c36 100644 --- a/src/tmuxp/config_reader.py +++ b/src/tmuxp/config_reader.py @@ -36,12 +36,15 @@ def _load(format: "FormatLiteral", content: str) -> t.Dict[str, t.Any]: {'session_name': 'my session'} """ if format == "yaml": - return yaml.load( - content, - Loader=yaml.SafeLoader, + return t.cast( + t.Dict[str, t.Any], + yaml.load( + content, + Loader=yaml.SafeLoader, + ), ) elif format == "json": - return json.loads(content) + return t.cast(t.Dict[str, t.Any], json.loads(content)) else: raise NotImplementedError(f"{format} not supported in configuration") From 071ec812f4c20205b54ee74685798ff74f6a15e5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 10:26:15 -0500 Subject: [PATCH 35/39] chore(mypy): Add typings exc --- src/tmuxp/exc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tmuxp/exc.py b/src/tmuxp/exc.py index 5fb0d9f722f..3d04c63735d 100644 --- a/src/tmuxp/exc.py +++ b/src/tmuxp/exc.py @@ -80,10 +80,10 @@ class TmuxpPluginException(TmuxpException): class BeforeLoadScriptNotExists(OSError): - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args: object, **kwargs: object) -> None: super().__init__(*args, **kwargs) - self.strerror = "before_script file '%s' doesn't exist." % self.strerror + self.strerror = f"before_script file '{self.strerror}' doesn't exist." @implements_to_string @@ -106,5 +106,5 @@ def __init__( f"{self.output}" ) - def __str__(self): + def __str__(self) -> str: return self.message From 4b95fd50a933b01021278e4dc9623cec441d0dc2 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 10:27:37 -0500 Subject: [PATCH 36/39] chore(mypy): Add typings for workspace/loader --- src/tmuxp/workspace/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tmuxp/workspace/loader.py b/src/tmuxp/workspace/loader.py index 9d7ee1d9b71..83da8deb105 100644 --- a/src/tmuxp/workspace/loader.py +++ b/src/tmuxp/workspace/loader.py @@ -30,7 +30,7 @@ def expandshell(value: str) -> str: return os.path.expandvars(os.path.expanduser(value)) # NOQA: PTH111 -def expand_cmd(p: t.Dict) -> t.Dict: +def expand_cmd(p: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: if isinstance(p, str): p = {"shell_command": [p]} elif isinstance(p, list): From f0e637db97499de61bd3d702c505582dd9c7df54 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 10:28:08 -0500 Subject: [PATCH 37/39] chore(mypy): Remove unused type: ignore from _compat --- src/tmuxp/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tmuxp/_compat.py b/src/tmuxp/_compat.py index 805d6c1cc5c..1b0009e3a1f 100644 --- a/src/tmuxp/_compat.py +++ b/src/tmuxp/_compat.py @@ -12,7 +12,7 @@ else: import pdb - breakpoint = pdb.set_trace # type: ignore + breakpoint = pdb.set_trace console_encoding = sys.__stdout__.encoding From 23fe049079b7446f51a6af91797b77828eacce20 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 10:30:24 -0500 Subject: [PATCH 38/39] chore(mypy): Add typings for log --- src/tmuxp/log.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tmuxp/log.py b/src/tmuxp/log.py index 3a18b67916a..b05b5ac7ef9 100644 --- a/src/tmuxp/log.py +++ b/src/tmuxp/log.py @@ -115,8 +115,8 @@ def template( return levelname + asctime + name - def __init__(self, color: bool = True, *args, **kwargs) -> None: - logging.Formatter.__init__(self, *args, **kwargs) + def __init__(self, color: bool = True, **kwargs: t.Any) -> None: + logging.Formatter.__init__(self, **kwargs) def format(self, record: logging.LogRecord) -> str: try: @@ -125,7 +125,7 @@ def format(self, record: logging.LogRecord) -> str: record.message = f"Bad message ({e!r}): {record.__dict__!r}" date_format = "%H:%m:%S" - formatting = self.converter(record.created) # type:ignore + formatting = self.converter(record.created) record.asctime = time.strftime(date_format, formatting) prefix = self.template(record) % record.__dict__ From b3737402fd651ffabbad0cfed2e78f0aba56dc90 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 23 Sep 2023 11:21:51 -0500 Subject: [PATCH 39/39] docs(CHANGES): Note mypy strict compliance --- CHANGES | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index bc92b1c36f4..da2ec723062 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,12 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force ### Development +- **Improved typings** + + Now [`mypy --strict`] compliant (#859) + + [`mypy --strict`]: https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-strict + - Poetry 1.5.1 -> 1.6.1 (#885) ## tmuxp 1.30.1 (2023-09-09) @@ -35,7 +41,7 @@ _Maintenance only, no bug fixes or new features_ - Cut last python 3.7 release (EOL was June 27th, 2023) - For security updates, a 1.30.x branch can be maintained for a limited time, + For security updates, a 1.30.x branch can be maintained for a limited time, if necessary. ## tmuxp 1.30.0 (2023-09-04) @@ -49,6 +55,7 @@ _Maintenance only, no bug fixes or new features_ This includes fixes made by hand alongside ruff's automated fixes. The more stringent rules include import sorting, and still runs almost instantly against the whole codebase. + - CI: `black . --check` now runs on pushes and pull requests ### Packaging