From d75829bb104de709565621175570ba6c004b34a1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 06:55:38 -0600 Subject: [PATCH 1/8] pyproject: Use future annotations --- pyproject.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 520c54aaf7..f335da8045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -192,16 +192,25 @@ select = [ "PERF", # Perflint "RUF", # Ruff-specific rules "D", # pydocstyle + "FA100", # future annotations ] ignore = [ "COM812", # missing trailing comma, ruff format conflict ] +extend-safe-fixes = [ + "UP006", + "UP007", +] +pyupgrade.keep-runtime-typing = false [tool.ruff.lint.isort] known-first-party = [ "tmuxp", ] combine-as-imports = true +required-imports = [ + "from __future__ import annotations", +] [tool.ruff.lint.pydocstyle] convention = "numpy" From 1912c08406455d10cad1a9c809e74f92d3cbb78d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 07:42:08 -0600 Subject: [PATCH 2/8] pyproject(tool.coverage.report) Add exclusions --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f335da8045..75dd57d217 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,6 +149,9 @@ exclude_lines = [ "raise NotImplementedError", "if __name__ == .__main__.:", "def parse_args", + "from __future__ import annotations", + "if TYPE_CHECKING:", + "if t.TYPE_CHECKING:", ] [tool.mypy] From 48ccd1ccb0215ec4f07bbbaaa618f78d2e33f267 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 09:58:49 -0600 Subject: [PATCH 3/8] cli(utils) Preserve `print` for `tmuxp_echo` --- src/tmuxp/cli/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tmuxp/cli/utils.py b/src/tmuxp/cli/utils.py index c101542c40..bb8666b7e2 100644 --- a/src/tmuxp/cli/utils.py +++ b/src/tmuxp/cli/utils.py @@ -30,7 +30,7 @@ def tmuxp_echo( else: logger.log(log.LOG_LEVELS[log_level], unstyle(message)) - print(message) + print(message) # NOQA: T201 RUF100 def prompt( From 696edb56546e12383147addfd578d8ea67321569 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 10:03:12 -0600 Subject: [PATCH 4/8] cli(utils) Preserve `print` for more areas --- src/tmuxp/cli/load.py | 4 ++-- src/tmuxp/cli/ls.py | 2 +- src/tmuxp/util.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index 551d9ecf29..b47f4fda75 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -115,7 +115,7 @@ def _reattach(builder: WorkspaceBuilder) -> None: plugin.reattach(builder.session) proc = builder.session.cmd("display-message", "-p", "'#S'") for line in proc.stdout: - print(line) + print(line) # NOQA: PT014 RUF100 if "TMUX" in os.environ: builder.session.switch_client() @@ -159,7 +159,7 @@ def _load_detached(builder: WorkspaceBuilder) -> None: assert builder.session is not None - print("Session created in detached state.") + print("Session created in detached state.") # NOQA: PT014 RUF100 def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None: diff --git a/src/tmuxp/cli/ls.py b/src/tmuxp/cli/ls.py index e5c0defcdc..cfe29f8f3d 100644 --- a/src/tmuxp/cli/ls.py +++ b/src/tmuxp/cli/ls.py @@ -25,4 +25,4 @@ def command_ls( stem, ext = os.path.splitext(f) if os.path.isdir(f) or ext not in VALID_WORKSPACE_DIR_FILE_EXTENSIONS: continue - print(stem) + print(stem) # NOQA: T201 RUF100 diff --git a/src/tmuxp/util.py b/src/tmuxp/util.py index 3efc5a900d..fe7a2a2b79 100644 --- a/src/tmuxp/util.py +++ b/src/tmuxp/util.py @@ -75,7 +75,7 @@ def oh_my_zsh_auto_title() -> None: or os.environ.get("DISABLE_AUTO_TITLE") == "false" ) ): - print( + print( # NOQA: T201 RUF100 "Please set:\n\n" "\texport DISABLE_AUTO_TITLE='true'\n\n" "in ~/.zshrc or where your zsh profile is stored.\n" @@ -155,7 +155,7 @@ def get_pane(window: "Window", current_pane: t.Optional["Pane"] = None) -> "Pane else: pane = window.active_pane except exc.TmuxpException as e: - print(e) + print(e) # NOQA: T201 RUF100 if pane is None: if current_pane: From 2099e4bd776d5db198b69cbea51054914c42545e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 10:12:27 -0600 Subject: [PATCH 5/8] tests(builder) Handle PT014 `pytest-duplicate-parametrize-test-cases` See also: https://docs.astral.sh/ruff/rules/pytest-duplicate-parametrize-test-cases/ --- tests/workspace/test_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index c5d2b608ae..9640cd6e31 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -1166,7 +1166,7 @@ def test_find_current_active_pane( "___4___", False, ), - ( + ( # NOQA: PT014 RUF100 textwrap.dedent( """ session_name: Should not execute @@ -1180,7 +1180,7 @@ def test_find_current_active_pane( "___4___", False, ), - ( + ( # NOQA: PT014 RUF100 textwrap.dedent( """ session_name: Should execute From 78ea4662dec6ea01093e336239fce8f8d50c6765 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 07:05:54 -0600 Subject: [PATCH 6/8] chore(ruff) Automated fixes for typing annotations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed 393 errors: - conftest.py: 1 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) - docs/_ext/aafig.py: 6 × UP007 (non-pep604-annotation) 3 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - docs/conf.py: 2 × UP007 (non-pep604-annotation) 2 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - src/tmuxp/__about__.py: 1 × I002 (missing-required-import) - src/tmuxp/__init__.py: 1 × I002 (missing-required-import) - src/tmuxp/_internal/config_reader.py: 8 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - src/tmuxp/_internal/types.py: 1 × I002 (missing-required-import) - src/tmuxp/cli/__init__.py: 3 × UP037 (quoted-annotation) 2 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) 1 × I001 (unsorted-imports) - src/tmuxp/cli/convert.py: 2 × UP007 (non-pep604-annotation) 1 × T201 (print) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) - src/tmuxp/cli/debug_info.py: 1 × UP007 (non-pep604-annotation) 1 × TC003 (typing-only-standard-library-import) 1 × I002 (missing-required-import) - src/tmuxp/cli/edit.py: 2 × TC003 (typing-only-standard-library-import) 2 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) 1 × I001 (unsorted-imports) - src/tmuxp/cli/freeze.py: 10 × UP007 (non-pep604-annotation) 5 × T201 (print) 3 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) 1 × F841 (unused-variable) - src/tmuxp/cli/import_config.py: 3 × UP007 (non-pep604-annotation) 1 × TC003 (typing-only-standard-library-import) 1 × I002 (missing-required-import) - src/tmuxp/cli/load.py: 15 × UP007 (non-pep604-annotation) 2 × T201 (print) 1 × UP037 (quoted-annotation) 1 × E303 (too-many-blank-lines) 1 × TC002 (typing-only-third-party-import) 1 × I001 (unsorted-imports) 1 × B007 (unused-loop-control-variable) 1 × I002 (missing-required-import) 1 × TC001 (typing-only-first-party-import) - src/tmuxp/cli/ls.py: 1 × UP007 (non-pep604-annotation) 1 × TC003 (typing-only-standard-library-import) 1 × I002 (missing-required-import) - src/tmuxp/cli/shell.py: 8 × UP007 (non-pep604-annotation) 2 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - src/tmuxp/cli/utils.py: 19 × UP007 (non-pep604-annotation) 3 × UP037 (quoted-annotation) 2 × TC003 (typing-only-standard-library-import) 1 × I002 (missing-required-import) 1 × I001 (unsorted-imports) - src/tmuxp/exc.py: 4 × UP007 (non-pep604-annotation) 1 × F401 (unused-import) 1 × I002 (missing-required-import) 1 × I001 (unsorted-imports) - src/tmuxp/log.py: 2 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) - src/tmuxp/plugin.py: 12 × UP007 (non-pep604-annotation) 12 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - src/tmuxp/shell.py: 18 × UP037 (quoted-annotation) 6 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) - src/tmuxp/types.py: 1 × I002 (missing-required-import) - src/tmuxp/util.py: 11 × UP037 (quoted-annotation) 8 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) 1 × I001 (unsorted-imports) - src/tmuxp/workspace/builder.py: 5 × UP007 (non-pep604-annotation) 2 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) - src/tmuxp/workspace/constants.py: 1 × I002 (missing-required-import) - src/tmuxp/workspace/finders.py: 4 × UP007 (non-pep604-annotation) 3 × UP037 (quoted-annotation) 1 × TC003 (typing-only-standard-library-import) 1 × I001 (unsorted-imports) 1 × I002 (missing-required-import) 1 × TC001 (typing-only-first-party-import) - src/tmuxp/workspace/freezer.py: 2 × UP007 (non-pep604-annotation) 2 × TC002 (typing-only-third-party-import) 2 × I001 (unsorted-imports) 1 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - src/tmuxp/workspace/importers.py: 1 × I002 (missing-required-import) - src/tmuxp/workspace/loader.py: 3 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) - src/tmuxp/workspace/validation.py: 1 × I002 (missing-required-import) - tests/cli/test_cli.py: 2 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - tests/cli/test_convert.py: 1 × I001 (unsorted-imports) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) - tests/cli/test_debug_info.py: 3 × I001 (unsorted-imports) 1 × TC003 (typing-only-standard-library-import) 1 × I002 (missing-required-import) 1 × TC002 (typing-only-third-party-import) - tests/cli/test_freeze.py: 2 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) 1 × I001 (unsorted-imports) - tests/cli/test_import.py: 1 × I001 (unsorted-imports) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) - tests/cli/test_load.py: 22 × UP037 (quoted-annotation) 1 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) 1 × TC002 (typing-only-third-party-import) - tests/cli/test_ls.py: 1 × I001 (unsorted-imports) 1 × TC002 (typing-only-third-party-import) 1 × I002 (missing-required-import) - tests/cli/test_shell.py: 3 × UP037 (quoted-annotation) 1 × TC002 (typing-only-third-party-import) 1 × PYI055 (unnecessary-type-union) 1 × I001 (unsorted-imports) 1 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) 1 × TC003 (typing-only-standard-library-import) - tests/constants.py: 1 × I002 (missing-required-import) - tests/fixtures/__init__.py: 1 × I002 (missing-required-import) - tests/fixtures/import_teamocil/__init__.py: 1 × I002 (missing-required-import) - tests/fixtures/import_teamocil/layouts.py: 1 × I002 (missing-required-import) - tests/fixtures/import_teamocil/test1.py: 1 × I002 (missing-required-import) - tests/fixtures/import_teamocil/test2.py: 1 × I002 (missing-required-import) - tests/fixtures/import_teamocil/test3.py: 1 × I002 (missing-required-import) - tests/fixtures/import_teamocil/test4.py: 1 × I002 (missing-required-import) - tests/fixtures/import_tmuxinator/__init__.py: 1 × I002 (missing-required-import) - tests/fixtures/import_tmuxinator/test1.py: 1 × I002 (missing-required-import) - tests/fixtures/import_tmuxinator/test2.py: 1 × I002 (missing-required-import) - tests/fixtures/import_tmuxinator/test3.py: 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/partials/_types.py: 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/partials/all_pass.py: 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/partials/libtmux_version_fail.py: 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/partials/test_plugin_helpers.py: 1 × I002 (missing-required-import) 1 × UP037 (quoted-annotation) - tests/fixtures/pluginsystem/partials/tmux_version_fail.py: 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py: 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/tmuxp_test_plugin_awf/plugin.py: 1 × I002 (missing-required-import) 1 × UP037 (quoted-annotation) - tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/tmuxp_test_plugin_bs/plugin.py: 1 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/tmuxp_test_plugin_bwb/plugin.py: 1 × UP037 (quoted-annotation) 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/tmuxp_test_plugin_fail/plugin.py: 1 × I002 (missing-required-import) - tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/tmuxp_test_plugin_owc/plugin.py: 1 × I002 (missing-required-import) 1 × UP037 (quoted-annotation) - tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/tmuxp_test_plugin_r/plugin.py: 1 × I002 (missing-required-import) 1 × UP037 (quoted-annotation) - tests/fixtures/structures.py: 1 × I002 (missing-required-import) - tests/fixtures/utils.py: 2 × UP007 (non-pep604-annotation) 1 × F401 (unused-import) 1 × I002 (missing-required-import) - tests/fixtures/workspace/__init__.py: 1 × I002 (missing-required-import) - tests/fixtures/workspace/expand1.py: 1 × I002 (missing-required-import) - tests/fixtures/workspace/expand2.py: 1 × I002 (missing-required-import) - tests/fixtures/workspace/expand_blank.py: 1 × I002 (missing-required-import) - tests/fixtures/workspace/sample_workspace.py: 1 × I002 (missing-required-import) - tests/fixtures/workspace/shell_command_before.py: 1 × I002 (missing-required-import) - tests/fixtures/workspace/shell_command_before_session.py: 1 × I002 (missing-required-import) - tests/fixtures/workspace/trickle.py: 1 × I002 (missing-required-import) - tests/test_plugin.py: 1 × I002 (missing-required-import) - tests/test_shell.py: 1 × I002 (missing-required-import) - tests/test_util.py: 1 × I001 (unsorted-imports) 1 × I002 (missing-required-import) 1 × TC002 (typing-only-third-party-import) - tests/tests/test_helpers.py: 1 × I001 (unsorted-imports) 1 × TC002 (typing-only-third-party-import) 1 × I002 (missing-required-import) - tests/workspace/conftest.py: 1 × I002 (missing-required-import) - tests/workspace/test_builder.py: 2 × UP037 (quoted-annotation) 2 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) 1 × TC002 (typing-only-third-party-import) - tests/workspace/test_config.py: 7 × UP037 (quoted-annotation) 1 × UP007 (non-pep604-annotation) 1 × I002 (missing-required-import) - tests/workspace/test_finder.py: 1 × I002 (missing-required-import) 1 × UP037 (quoted-annotation) - tests/workspace/test_freezer.py: 2 × I001 (unsorted-imports) 1 × I002 (missing-required-import) 1 × UP037 (quoted-annotation) 1 × TC003 (typing-only-standard-library-import) 1 × TC002 (typing-only-third-party-import) - tests/workspace/test_import_teamocil.py: 1 × I002 (missing-required-import) - tests/workspace/test_import_tmuxinator.py: 1 × I002 (missing-required-import) Found 1480 errors (393 fixed, 1087 remaining). 83 files reformatted, 16 files left unchanged uv run ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; uv run ruff format . --- conftest.py | 4 +- docs/_ext/aafig.py | 18 ++++--- docs/conf.py | 10 ++-- src/tmuxp/__about__.py | 2 + src/tmuxp/__init__.py | 2 + src/tmuxp/_internal/config_reader.py | 16 +++--- src/tmuxp/_internal/types.py | 2 + src/tmuxp/cli/__init__.py | 13 +++-- src/tmuxp/cli/convert.py | 10 ++-- src/tmuxp/cli/debug_info.py | 8 ++- src/tmuxp/cli/edit.py | 12 +++-- src/tmuxp/cli/freeze.py | 40 ++++++--------- src/tmuxp/cli/import_config.py | 12 +++-- src/tmuxp/cli/load.py | 41 ++++++++------- src/tmuxp/cli/ls.py | 8 ++- src/tmuxp/cli/shell.py | 18 ++++--- src/tmuxp/cli/utils.py | 45 +++++++++-------- src/tmuxp/exc.py | 10 ++-- src/tmuxp/log.py | 6 ++- src/tmuxp/plugin.py | 48 +++++++++--------- src/tmuxp/shell.py | 40 ++++++++------- src/tmuxp/types.py | 2 + src/tmuxp/util.py | 29 ++++++----- src/tmuxp/workspace/builder.py | 18 ++++--- src/tmuxp/workspace/constants.py | 2 + src/tmuxp/workspace/finders.py | 16 +++--- src/tmuxp/workspace/freezer.py | 13 ++--- src/tmuxp/workspace/importers.py | 2 + src/tmuxp/workspace/loader.py | 6 ++- src/tmuxp/workspace/validation.py | 2 + tests/cli/test_cli.py | 6 ++- tests/cli/test_convert.py | 7 ++- tests/cli/test_debug_info.py | 9 +++- tests/cli/test_freeze.py | 9 ++-- tests/cli/test_import.py | 7 ++- tests/cli/test_load.py | 50 ++++++++++--------- tests/cli/test_ls.py | 8 ++- tests/cli/test_shell.py | 18 +++---- tests/constants.py | 2 + tests/fixtures/__init__.py | 2 + tests/fixtures/import_teamocil/__init__.py | 2 + tests/fixtures/import_teamocil/layouts.py | 2 + tests/fixtures/import_teamocil/test1.py | 2 + tests/fixtures/import_teamocil/test2.py | 2 + tests/fixtures/import_teamocil/test3.py | 2 + tests/fixtures/import_teamocil/test4.py | 2 + tests/fixtures/import_tmuxinator/__init__.py | 2 + tests/fixtures/import_tmuxinator/test1.py | 2 + tests/fixtures/import_tmuxinator/test2.py | 2 + tests/fixtures/import_tmuxinator/test3.py | 2 + .../fixtures/pluginsystem/partials/_types.py | 2 + .../pluginsystem/partials/all_pass.py | 2 + .../partials/libtmux_version_fail.py | 2 + .../partials/test_plugin_helpers.py | 4 +- .../partials/tmux_version_fail.py | 2 + .../partials/tmuxp_version_fail.py | 2 + .../tmuxp_test_plugin_awf/plugin.py | 4 +- .../tmuxp_test_plugin_bs/plugin.py | 4 +- .../tmuxp_test_plugin_bwb/plugin.py | 4 +- .../tmuxp_test_plugin_fail/plugin.py | 2 + .../tmuxp_test_plugin_owc/plugin.py | 4 +- .../tmuxp_test_plugin_r/plugin.py | 4 +- tests/fixtures/structures.py | 2 + tests/fixtures/utils.py | 7 +-- tests/fixtures/workspace/__init__.py | 2 + tests/fixtures/workspace/expand1.py | 2 + tests/fixtures/workspace/expand2.py | 2 + tests/fixtures/workspace/expand_blank.py | 2 + tests/fixtures/workspace/sample_workspace.py | 2 + .../workspace/shell_command_before.py | 2 + .../workspace/shell_command_before_session.py | 2 + tests/fixtures/workspace/trickle.py | 2 + tests/test_plugin.py | 2 + tests/test_shell.py | 2 + tests/test_util.py | 8 ++- tests/tests/test_helpers.py | 8 ++- tests/workspace/conftest.py | 2 + tests/workspace/test_builder.py | 12 +++-- tests/workspace/test_config.py | 18 ++++--- tests/workspace/test_finder.py | 4 +- tests/workspace/test_freezer.py | 11 ++-- tests/workspace/test_import_teamocil.py | 2 + tests/workspace/test_import_tmuxinator.py | 2 + 83 files changed, 456 insertions(+), 269 deletions(-) diff --git a/conftest.py b/conftest.py index 54d0b18bb0..4a22edb1db 100644 --- a/conftest.py +++ b/conftest.py @@ -8,6 +8,8 @@ https://docs.pytest.org/en/stable/deprecations.html """ +from __future__ import annotations + import logging import os import pathlib @@ -29,7 +31,7 @@ @pytest.fixture(autouse=USING_ZSH, scope="session") -def zshrc(user_path: pathlib.Path) -> t.Optional[pathlib.Path]: +def zshrc(user_path: pathlib.Path) -> pathlib.Path | None: """Quiets ZSH default message. Needs a startup file .zshenv, .zprofile, .zshrc, .zlogin. diff --git a/docs/_ext/aafig.py b/docs/_ext/aafig.py index 987cc39825..ae83db35f0 100644 --- a/docs/_ext/aafig.py +++ b/docs/_ext/aafig.py @@ -12,6 +12,8 @@ :license: BOLA, see LICENSE for details """ +from __future__ import annotations + import locale import logging import posixpath @@ -40,9 +42,9 @@ def merge_dict( - dst: dict[str, t.Optional[str]], - src: dict[str, t.Optional[str]], -) -> dict[str, t.Optional[str]]: + dst: dict[str, str | None], + src: dict[str, str | None], +) -> dict[str, str | None]: for k, v in src.items(): if k not in dst: dst[k] = v @@ -52,7 +54,7 @@ def merge_dict( def get_basename( text: str, options: dict[str, str], - prefix: t.Optional[str] = "aafig", + prefix: str | None = "aafig", ) -> str: options = options.copy() if "format" in options: @@ -105,7 +107,7 @@ def run(self) -> list[nodes.Node]: return [image_node] -def render_aafig_images(app: "Sphinx", doctree: nodes.Node) -> None: +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: @@ -157,10 +159,10 @@ def __init__(self, *args: object, **kwargs: object) -> None: def render_aafigure( - app: "Sphinx", + app: Sphinx, text: str, options: dict[str, str], -) -> tuple[str, str, t.Optional[str], t.Optional[str]]: +) -> tuple[str, str, str | None, str | None]: """Render an ASCII art figure into the requested format output file.""" if aafigure is None: raise AafigureNotInstalled @@ -227,7 +229,7 @@ def render_aafigure( return relfn, outfn, None, extra -def setup(app: "Sphinx") -> None: +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") diff --git a/docs/conf.py b/docs/conf.py index 980b848890..81c72be345 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,8 @@ # flake8: NOQA: E501 """Sphinx documentation configuration for tmuxp.""" +from __future__ import annotations + import contextlib import inspect import pathlib @@ -74,7 +76,7 @@ html_favicon = "_static/favicon.ico" html_theme = "furo" html_theme_path: list[str] = [] -html_theme_options: dict[str, t.Union[str, list[dict[str, str]]]] = { +html_theme_options: dict[str, str | list[dict[str, str]]] = { "light_logo": "img/tmuxp.svg", "dark_logo": "img/tmuxp.svg", "footer_icons": [ @@ -143,7 +145,7 @@ } -def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]: +def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str: """ Determine the URL corresponding to Python object. @@ -213,7 +215,7 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]: ) -def remove_tabs_js(app: "Sphinx", exc: Exception) -> None: +def remove_tabs_js(app: Sphinx, exc: Exception) -> None: """Fix for sphinx-inline-tabs#18.""" if app.builder.format == "html" and not exc: tabs_js = pathlib.Path(app.builder.outdir) / "_static" / "tabs.js" @@ -221,6 +223,6 @@ def remove_tabs_js(app: "Sphinx", exc: Exception) -> None: tabs_js.unlink() # When python 3.7 deprecated, use missing_ok=True -def setup(app: "Sphinx") -> None: +def setup(app: Sphinx) -> None: """Sphinx setup hook.""" app.connect("build-finished", remove_tabs_js) diff --git a/src/tmuxp/__about__.py b/src/tmuxp/__about__.py index 0968fbb7c9..1e9b274088 100644 --- a/src/tmuxp/__about__.py +++ b/src/tmuxp/__about__.py @@ -1,5 +1,7 @@ """Metadata for tmuxp package.""" +from __future__ import annotations + __title__ = "tmuxp" __package_name__ = "tmuxp" __version__ = "1.50.1" diff --git a/src/tmuxp/__init__.py b/src/tmuxp/__init__.py index a7d3e623e0..ac9f8422e7 100644 --- a/src/tmuxp/__init__.py +++ b/src/tmuxp/__init__.py @@ -5,6 +5,8 @@ :license: MIT, see LICENSE for details """ +from __future__ import annotations + from . import cli, util from .__about__ import ( __author__, diff --git a/src/tmuxp/_internal/config_reader.py b/src/tmuxp/_internal/config_reader.py index 6cd862edb9..f7db20e48d 100644 --- a/src/tmuxp/_internal/config_reader.py +++ b/src/tmuxp/_internal/config_reader.py @@ -1,5 +1,7 @@ """Configuration parser for YAML and JSON files.""" +from __future__ import annotations + import json import pathlib import typing as t @@ -24,11 +26,11 @@ class ConfigReader: '{\n "session_name": "my session"\n}' """ - def __init__(self, content: "RawConfigData") -> None: + def __init__(self, content: RawConfigData) -> None: self.content = content @staticmethod - def _load(fmt: "FormatLiteral", content: str) -> dict[str, t.Any]: + def _load(fmt: FormatLiteral, content: str) -> dict[str, t.Any]: """Load raw config data and directly return it. >>> ConfigReader._load("json", '{ "session_name": "my session" }') @@ -51,7 +53,7 @@ def _load(fmt: "FormatLiteral", content: str) -> dict[str, t.Any]: raise NotImplementedError(msg) @classmethod - def load(cls, fmt: "FormatLiteral", content: str) -> "ConfigReader": + def load(cls, fmt: FormatLiteral, content: str) -> ConfigReader: """Load raw config data into a ConfigReader instance (to dump later). >>> cfg = ConfigReader.load("json", '{ "session_name": "my session" }') @@ -120,7 +122,7 @@ def _from_file(cls, path: pathlib.Path) -> dict[str, t.Any]: ) @classmethod - def from_file(cls, path: pathlib.Path) -> "ConfigReader": + def from_file(cls, path: pathlib.Path) -> ConfigReader: r"""Load data from file path. **YAML file** @@ -161,8 +163,8 @@ def from_file(cls, path: pathlib.Path) -> "ConfigReader": @staticmethod def _dump( - fmt: "FormatLiteral", - content: "RawConfigData", + fmt: FormatLiteral, + content: RawConfigData, indent: int = 2, **kwargs: t.Any, ) -> str: @@ -189,7 +191,7 @@ def _dump( msg = f"{fmt} not supported in config" raise NotImplementedError(msg) - def dump(self, fmt: "FormatLiteral", indent: int = 2, **kwargs: t.Any) -> str: + def dump(self, fmt: FormatLiteral, indent: int = 2, **kwargs: t.Any) -> str: r"""Dump via ConfigReader instance. >>> cfg = ConfigReader({ "session_name": "my session" }) diff --git a/src/tmuxp/_internal/types.py b/src/tmuxp/_internal/types.py index aa2d9444db..c66d88956c 100644 --- a/src/tmuxp/_internal/types.py +++ b/src/tmuxp/_internal/types.py @@ -10,6 +10,8 @@ ... """ +from __future__ import annotations + from typing_extensions import NotRequired, TypedDict diff --git a/src/tmuxp/cli/__init__.py b/src/tmuxp/cli/__init__.py index f285cb7d9c..8af6a50547 100644 --- a/src/tmuxp/cli/__init__.py +++ b/src/tmuxp/cli/__init__.py @@ -1,9 +1,10 @@ """CLI utilities for tmuxp.""" +from __future__ import annotations + import argparse import logging import os -import pathlib import sys import typing as t @@ -32,6 +33,8 @@ logger = logging.getLogger(__name__) if t.TYPE_CHECKING: + import pathlib + from typing_extensions import TypeAlias CLIVerbosity: TypeAlias = t.Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] @@ -109,16 +112,16 @@ def create_parser() -> argparse.ArgumentParser: class CLINamespace(argparse.Namespace): """Typed :class:`argparse.Namespace` for tmuxp root-level CLI.""" - log_level: "CLIVerbosity" - subparser_name: "CLISubparserName" - import_subparser_name: t.Optional["CLIImportSubparserName"] + log_level: CLIVerbosity + subparser_name: CLISubparserName + import_subparser_name: CLIImportSubparserName | None version: bool ns = CLINamespace() -def cli(_args: t.Optional[list[str]] = None) -> None: +def cli(_args: list[str] | None = None) -> None: """Manage tmux sessions. Pass the "--help" argument to any command to see detailed help. diff --git a/src/tmuxp/cli/convert.py b/src/tmuxp/cli/convert.py index 0d9ee5173d..d2f13087d7 100644 --- a/src/tmuxp/cli/convert.py +++ b/src/tmuxp/cli/convert.py @@ -1,6 +1,7 @@ """CLI for ``tmuxp convert`` subcommand.""" -import argparse +from __future__ import annotations + import locale import os import pathlib @@ -13,6 +14,8 @@ from .utils import prompt_yes_no if t.TYPE_CHECKING: + import argparse + AllowedFileTypes = t.Literal["json", "yaml"] @@ -53,9 +56,9 @@ def __init__(self, ext: str, *args: object, **kwargs: object) -> None: def command_convert( - workspace_file: t.Union[str, pathlib.Path], + workspace_file: str | pathlib.Path, answer_yes: bool, - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp convert`` convert a tmuxp config between JSON and YAML.""" workspace_file = find_workspace_file( @@ -95,4 +98,3 @@ def command_convert( if answer_yes: with open(newfile, "w", encoding=locale.getpreferredencoding(False)) as buf: buf.write(new_workspace) - print(f"New workspace file saved to <{newfile}>.") diff --git a/src/tmuxp/cli/debug_info.py b/src/tmuxp/cli/debug_info.py index f8908a5ba2..fdd55c83fd 100644 --- a/src/tmuxp/cli/debug_info.py +++ b/src/tmuxp/cli/debug_info.py @@ -1,6 +1,7 @@ """CLI for ``tmuxp debug-info`` subcommand.""" -import argparse +from __future__ import annotations + import os import pathlib import platform @@ -16,6 +17,9 @@ from .utils import tmuxp_echo +if t.TYPE_CHECKING: + import argparse + tmuxp_path = pathlib.Path(__file__).parent.parent @@ -27,7 +31,7 @@ def create_debug_info_subparser( def command_debug_info( - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp debug-info`` to print debug info to submit with issues.""" diff --git a/src/tmuxp/cli/edit.py b/src/tmuxp/cli/edit.py index 52bdfaf00b..075ca201dd 100644 --- a/src/tmuxp/cli/edit.py +++ b/src/tmuxp/cli/edit.py @@ -1,13 +1,17 @@ """CLI for ``tmuxp edit`` subcommand.""" -import argparse +from __future__ import annotations + import os -import pathlib import subprocess import typing as t from tmuxp.workspace.finders import find_workspace_file +if t.TYPE_CHECKING: + import argparse + import pathlib + def create_edit_subparser( parser: argparse.ArgumentParser, @@ -23,8 +27,8 @@ def create_edit_subparser( def command_edit( - workspace_file: t.Union[str, pathlib.Path], - parser: t.Optional[argparse.ArgumentParser] = None, + workspace_file: str | pathlib.Path, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp edit``, open tmuxp workspace file in system editor.""" workspace_file = find_workspace_file(workspace_file) diff --git a/src/tmuxp/cli/freeze.py b/src/tmuxp/cli/freeze.py index b6d4e27617..772a60e6fa 100644 --- a/src/tmuxp/cli/freeze.py +++ b/src/tmuxp/cli/freeze.py @@ -1,5 +1,7 @@ """CLI for ``tmuxp freeze`` subcommand.""" +from __future__ import annotations + import argparse import locale import os @@ -27,13 +29,13 @@ class CLIFreezeNamespace(argparse.Namespace): """Typed :class:`argparse.Namespace` for tmuxp freeze command.""" session_name: str - socket_name: t.Optional[str] - socket_path: t.Optional[str] - workspace_format: t.Optional["CLIOutputFormatLiteral"] - save_to: t.Optional[str] - answer_yes: t.Optional[bool] - quiet: t.Optional[bool] - force: t.Optional[bool] + socket_name: str | None + socket_path: str | None + workspace_format: CLIOutputFormatLiteral | None + save_to: str | None + answer_yes: bool | None + quiet: bool | None + force: bool | None def create_freeze_subparser( @@ -97,7 +99,7 @@ def create_freeze_subparser( def command_freeze( args: CLIFreezeNamespace, - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp freeze``, snapshot a tmux session into a tmuxp workspace. @@ -114,8 +116,7 @@ def command_freeze( if not session: raise exc.SessionNotFound - except TmuxpException as e: - print(e) + except TmuxpException: return frozen_workspace = freezer.freeze(session) @@ -123,11 +124,7 @@ def command_freeze( configparser = ConfigReader(workspace) if not args.quiet: - print( - "---------------------------------------------------------------" - "\n" - "Freeze does its best to snapshot live tmux sessions.\n", - ) + pass if not ( args.answer_yes or prompt_yes_no( @@ -135,11 +132,7 @@ def command_freeze( ) ): if not args.quiet: - print( - "tmuxp has examples in JSON and YAML format at " - "\n" - "View tmuxp docs at .", - ) + pass sys.exit() dest = args.save_to @@ -158,7 +151,6 @@ def command_freeze( default=save_to, ) if not args.force and os.path.exists(dest_prompt): - print(f"{dest_prompt} exists. Pick a new filename.") continue dest = dest_prompt @@ -167,14 +159,14 @@ def command_freeze( valid_workspace_formats: list[CLIOutputFormatLiteral] = ["json", "yaml"] - def is_valid_ext(stem: t.Optional[str]) -> "TypeGuard[CLIOutputFormatLiteral]": + def is_valid_ext(stem: str | None) -> TypeGuard[CLIOutputFormatLiteral]: return stem in valid_workspace_formats if not is_valid_ext(workspace_format): def extract_workspace_format( val: str, - ) -> t.Optional["CLIOutputFormatLiteral"]: + ) -> CLIOutputFormatLiteral | None: suffix = pathlib.Path(val).suffix if isinstance(suffix, str): suffix = suffix.lower().lstrip(".") @@ -212,4 +204,4 @@ def extract_workspace_format( buf.write(workspace) if not args.quiet: - print(f"Saved to {dest}.") + pass diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index 1f4127b61d..a014e1558e 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -1,6 +1,7 @@ """CLI for ``tmuxp shell`` subcommand.""" -import argparse +from __future__ import annotations + import locale import os import pathlib @@ -13,6 +14,9 @@ from .utils import prompt, prompt_choices, prompt_yes_no, tmuxp_echo +if t.TYPE_CHECKING: + import argparse + def get_tmuxinator_dir() -> pathlib.Path: """Return tmuxinator configuration directory. @@ -136,7 +140,7 @@ def __call__(self, workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: def import_config( workspace_file: str, importfunc: ImportConfigFn, - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Import a configuration from a workspace_file.""" existing_workspace_file = ConfigReader._from_file(pathlib.Path(workspace_file)) @@ -189,7 +193,7 @@ def import_config( def command_import_tmuxinator( workspace_file: str, - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp import tmuxinator`` subcommand. @@ -205,7 +209,7 @@ def command_import_tmuxinator( def command_import_teamocil( workspace_file: str, - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp import teamocil`` subcommand. diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index b47f4fda75..fa65e1b8f5 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -1,5 +1,7 @@ """CLI for ``tmuxp load`` subcommand.""" +from __future__ import annotations + import argparse import importlib import logging @@ -10,11 +12,9 @@ import typing as t from libtmux.server import Server -from libtmux.session import Session from tmuxp import exc, log, util from tmuxp._internal import config_reader -from tmuxp.types import StrPath from tmuxp.workspace import loader from tmuxp.workspace.builder import WorkspaceBuilder from tmuxp.workspace.finders import find_workspace_file, get_workspace_dir @@ -22,29 +22,32 @@ from .utils import prompt_choices, prompt_yes_no, style, tmuxp_echo if t.TYPE_CHECKING: + from libtmux.session import Session from typing_extensions import NotRequired, TypeAlias, TypedDict + from tmuxp.types import StrPath + CLIColorsLiteral: TypeAlias = t.Literal[56, 88] class OptionOverrides(TypedDict): """Optional argument overrides for tmuxp load.""" detached: NotRequired[bool] - new_session_name: NotRequired[t.Optional[str]] + new_session_name: NotRequired[str | None] class CLILoadNamespace(argparse.Namespace): """Typed :class:`argparse.Namespace` for tmuxp load command.""" workspace_files: list[str] - socket_name: t.Optional[str] - socket_path: t.Optional[str] - tmux_config_file: t.Optional[str] - new_session_name: t.Optional[str] - answer_yes: t.Optional[bool] - append: t.Optional[bool] - colors: t.Optional["CLIColorsLiteral"] - log_file: t.Optional[str] + socket_name: str | None + socket_path: str | None + tmux_config_file: str | None + new_session_name: str | None + answer_yes: bool | None + append: bool | None + colors: CLIColorsLiteral | None + log_file: str | None def load_plugins(session_config: dict[str, t.Any]) -> list[t.Any]: @@ -115,7 +118,7 @@ def _reattach(builder: WorkspaceBuilder) -> None: plugin.reattach(builder.session) proc = builder.session.cmd("display-message", "-p", "'#S'") for line in proc.stdout: - print(line) # NOQA: PT014 RUF100 + print(line) # NOQA: T201 RUF100 if "TMUX" in os.environ: builder.session.switch_client() @@ -159,7 +162,7 @@ def _load_detached(builder: WorkspaceBuilder) -> None: assert builder.session is not None - print("Session created in detached state.") # NOQA: PT014 RUF100 + print("Session created in detached state.") # NOQA: T201 RUF100 def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None: @@ -191,15 +194,15 @@ def _setup_plugins(builder: WorkspaceBuilder) -> Session: def load_workspace( workspace_file: StrPath, - socket_name: t.Optional[str] = None, + socket_name: str | None = None, socket_path: None = None, - tmux_config_file: t.Optional[str] = None, - new_session_name: t.Optional[str] = None, - colors: t.Optional[int] = None, + tmux_config_file: str | None = None, + new_session_name: str | None = None, + colors: int | None = None, detached: bool = False, answer_yes: bool = False, append: bool = False, -) -> t.Optional[Session]: +) -> Session | None: """Entrypoint for ``tmuxp load``, load a tmuxp "workspace" session via config file. Parameters @@ -507,7 +510,7 @@ def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP def command_load( args: CLILoadNamespace, - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Load a tmux workspace from each WORKSPACE_FILE. diff --git a/src/tmuxp/cli/ls.py b/src/tmuxp/cli/ls.py index cfe29f8f3d..e1a8c7a610 100644 --- a/src/tmuxp/cli/ls.py +++ b/src/tmuxp/cli/ls.py @@ -1,12 +1,16 @@ """CLI for ``tmuxp ls`` subcommand.""" -import argparse +from __future__ import annotations + import os import typing as t from tmuxp.workspace.constants import VALID_WORKSPACE_DIR_FILE_EXTENSIONS from tmuxp.workspace.finders import get_workspace_dir +if t.TYPE_CHECKING: + import argparse + def create_ls_subparser( parser: argparse.ArgumentParser, @@ -16,7 +20,7 @@ def create_ls_subparser( def command_ls( - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp ls`` subcommand.""" tmuxp_dir = get_workspace_dir() diff --git a/src/tmuxp/cli/shell.py b/src/tmuxp/cli/shell.py index eee241a032..c3b005c287 100644 --- a/src/tmuxp/cli/shell.py +++ b/src/tmuxp/cli/shell.py @@ -1,5 +1,7 @@ """CLI for ``tmuxp shell`` subcommand.""" +from __future__ import annotations + import argparse import os import pathlib @@ -29,13 +31,13 @@ class CLIShellNamespace(argparse.Namespace): """Typed :class:`argparse.Namespace` for tmuxp shell command.""" session_name: str - socket_name: t.Optional[str] - socket_path: t.Optional[str] - colors: t.Optional["CLIColorsLiteral"] - log_file: t.Optional[str] - window_name: t.Optional[str] - command: t.Optional[str] - shell: t.Optional["CLIShellLiteral"] + socket_name: str | None + socket_path: str | None + colors: CLIColorsLiteral | None + log_file: str | None + window_name: str | None + command: str | None + shell: CLIShellLiteral | None use_pythonrc: bool use_vi_mode: bool @@ -147,7 +149,7 @@ def create_shell_subparser(parser: argparse.ArgumentParser) -> argparse.Argument def command_shell( args: CLIShellNamespace, - parser: t.Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> None: """Entrypoint for ``tmuxp shell`` for tmux server, session, window and pane. diff --git a/src/tmuxp/cli/utils.py b/src/tmuxp/cli/utils.py index bb8666b7e2..dbb40b08b7 100644 --- a/src/tmuxp/cli/utils.py +++ b/src/tmuxp/cli/utils.py @@ -1,13 +1,16 @@ """CLI utility helpers for tmuxp.""" +from __future__ import annotations + import logging import re import typing as t -from collections.abc import Callable, Sequence from tmuxp import log if t.TYPE_CHECKING: + from collections.abc import Callable, Sequence + from typing_extensions import TypeAlias CLIColour: TypeAlias = t.Union[int, tuple[int, int, int], str] @@ -17,7 +20,7 @@ def tmuxp_echo( - message: t.Optional[str] = None, + message: str | None = None, log_level: str = "INFO", style_log: bool = False, ) -> None: @@ -35,8 +38,8 @@ def tmuxp_echo( def prompt( name: str, - default: t.Optional[str] = None, - value_proc: t.Optional[Callable[[str], str]] = None, + default: str | None = None, + value_proc: Callable[[str], str] | None = None, ) -> str: """Return user input from command line. @@ -77,8 +80,8 @@ def prompt( def prompt_bool( name: str, default: bool = False, - yes_choices: t.Optional[Sequence[t.Any]] = None, - no_choices: t.Optional[Sequence[t.Any]] = None, + yes_choices: Sequence[t.Any] | None = None, + no_choices: Sequence[t.Any] | None = None, ) -> bool: """Return True / False by prompting user input from command line. @@ -127,10 +130,10 @@ def prompt_yes_no(name: str, default: bool = True) -> bool: def prompt_choices( name: str, - choices: t.Union[list[str], tuple[str, str]], - default: t.Optional[str] = None, + choices: list[str] | tuple[str, str], + default: str | None = None, no_choice: Sequence[str] = ("none",), -) -> t.Optional[str]: +) -> str | None: """Return user input from command line from set of provided choices. Parameters @@ -202,7 +205,7 @@ def strip_ansi(value: str) -> str: def _interpret_color( - color: t.Union[int, tuple[int, int, int], str], + color: int | tuple[int, int, int] | str, offset: int = 0, ) -> str: if isinstance(color, int): @@ -218,22 +221,22 @@ def _interpret_color( class UnknownStyleColor(Exception): """Raised when encountering an unknown terminal style color.""" - def __init__(self, color: "CLIColour", *args: object, **kwargs: object) -> None: + def __init__(self, color: CLIColour, *args: object, **kwargs: object) -> None: return super().__init__(f"Unknown color {color!r}", *args, **kwargs) def style( text: t.Any, - fg: t.Optional["CLIColour"] = None, - bg: t.Optional["CLIColour"] = None, - bold: t.Optional[bool] = None, - dim: t.Optional[bool] = None, - underline: t.Optional[bool] = None, - overline: t.Optional[bool] = None, - italic: t.Optional[bool] = None, - blink: t.Optional[bool] = None, - reverse: t.Optional[bool] = None, - strikethrough: t.Optional[bool] = None, + fg: CLIColour | None = None, + bg: CLIColour | None = None, + bold: bool | None = None, + dim: bool | None = None, + underline: bool | None = None, + overline: bool | None = None, + italic: bool | None = None, + blink: bool | None = None, + reverse: bool | None = None, + strikethrough: bool | None = None, reset: bool = True, ) -> str: """Credit: click.""" diff --git a/src/tmuxp/exc.py b/src/tmuxp/exc.py index 118ba00bcd..525599270f 100644 --- a/src/tmuxp/exc.py +++ b/src/tmuxp/exc.py @@ -1,6 +1,6 @@ """Exceptions for tmuxp.""" -import typing as t +from __future__ import annotations from libtmux._internal.query_list import ObjectDoesNotExist @@ -20,7 +20,7 @@ class SessionNotFound(TmuxpException): def __init__( self, - session_target: t.Optional[str] = None, + session_target: str | None = None, *args: object, **kwargs: object, ) -> None: @@ -35,7 +35,7 @@ class WindowNotFound(TmuxpException): def __init__( self, - window_target: t.Optional[str] = None, + window_target: str | None = None, *args: object, **kwargs: object, ) -> None: @@ -50,7 +50,7 @@ class PaneNotFound(TmuxpException): def __init__( self, - pane_target: t.Optional[str] = None, + pane_target: str | None = None, *args: object, **kwargs: object, ) -> None: @@ -115,7 +115,7 @@ def __init__( self, returncode: int, cmd: str, - output: t.Optional[str] = None, + output: str | None = None, ) -> None: self.returncode = returncode self.cmd = cmd diff --git a/src/tmuxp/log.py b/src/tmuxp/log.py index 57855dcabb..613b140a65 100644 --- a/src/tmuxp/log.py +++ b/src/tmuxp/log.py @@ -1,6 +1,8 @@ #!/usr/bin/env python """Log utilities for tmuxp.""" +from __future__ import annotations + import logging import time import typing as t @@ -26,7 +28,7 @@ def setup_logger( - logger: t.Optional[logging.Logger] = None, + logger: logging.Logger | None = None, level: str = "INFO", ) -> None: """Configure tmuxp's logging for CLI use. @@ -137,7 +139,7 @@ def format(self, record: logging.LogRecord) -> str: def debug_log_template( self: type[logging.Formatter], record: logging.LogRecord, - stylized: t.Optional[bool] = False, + stylized: bool | None = False, **kwargs: t.Any, ) -> str: """ diff --git a/src/tmuxp/plugin.py b/src/tmuxp/plugin.py index 9920e873f4..4873112c8b 100644 --- a/src/tmuxp/plugin.py +++ b/src/tmuxp/plugin.py @@ -1,5 +1,7 @@ """Plugin system for tmuxp.""" +from __future__ import annotations + import typing as t import libtmux @@ -38,10 +40,10 @@ class VersionConstraints(TypedDict): """Version constraints mapping for a tmuxp plugin.""" - version: t.Union[Version, str] + version: Version | str vmin: str - vmax: t.Optional[str] - incompatible: list[t.Union[t.Any, str]] + vmax: str | None + incompatible: list[t.Any | str] class TmuxpPluginVersionConstraints(TypedDict): """Version constraints for a tmuxp plugin.""" @@ -56,17 +58,17 @@ class Config(t.TypedDict): plugin_name: str tmux_min_version: str - tmux_max_version: t.Optional[str] - tmux_version_incompatible: t.Optional[list[str]] + tmux_max_version: str | None + tmux_version_incompatible: list[str] | None libtmux_min_version: str - libtmux_max_version: t.Optional[str] - libtmux_version_incompatible: t.Optional[list[str]] + libtmux_max_version: str | None + libtmux_version_incompatible: list[str] | None tmuxp_min_version: str - tmuxp_max_version: t.Optional[str] - tmuxp_version_incompatible: t.Optional[list[str]] + tmuxp_max_version: str | None + tmuxp_version_incompatible: list[str] | None -DEFAULT_CONFIG: "Config" = { +DEFAULT_CONFIG: Config = { "plugin_name": "tmuxp-plugin", "tmux_min_version": TMUX_MIN_VERSION, "tmux_max_version": TMUX_MAX_VERSION, @@ -80,15 +82,15 @@ class Config(t.TypedDict): } -def validate_plugin_config(config: "PluginConfigSchema") -> "TypeGuard[Config]": +def validate_plugin_config(config: PluginConfigSchema) -> TypeGuard[Config]: """Return True if tmuxp plugin configuration valid, also upcasts.""" return isinstance(config, dict) def setup_plugin_config( - config: "PluginConfigSchema", - default_config: "Config" = DEFAULT_CONFIG, -) -> "Config": + config: PluginConfigSchema, + default_config: Config = DEFAULT_CONFIG, +) -> Config: """Initialize tmuxp plugin configuration.""" new_config = config.copy() for default_key, default_value in default_config.items(): @@ -103,7 +105,7 @@ def setup_plugin_config( class TmuxpPlugin: """Base class for a tmuxp plugin.""" - def __init__(self, **kwargs: "Unpack[PluginConfigSchema]") -> None: + def __init__(self, **kwargs: Unpack[PluginConfigSchema]) -> None: """ Initialize plugin. @@ -197,10 +199,10 @@ def _version_check(self) -> None: def _pass_version_check( self, - version: t.Union[str, Version], + version: str | Version, vmin: str, - vmax: t.Optional[str], - incompatible: list[t.Union[t.Any, str]], + vmax: str | None, + incompatible: list[t.Any | str], ) -> bool: """Provide affirmative if version compatibility is correct.""" if vmin and version < Version(vmin): @@ -209,7 +211,7 @@ def _pass_version_check( return False return version not in incompatible - def before_workspace_builder(self, session: "Session") -> None: + def before_workspace_builder(self, session: Session) -> None: """ Provide a session hook previous to creating the workspace. @@ -222,7 +224,7 @@ def before_workspace_builder(self, session: "Session") -> None: session to hook into """ - def on_window_create(self, window: "Window") -> None: + def on_window_create(self, window: Window) -> None: """ Provide a window hook previous to doing anything with a window. @@ -234,7 +236,7 @@ def on_window_create(self, window: "Window") -> None: window to hook into """ - def after_window_finished(self, window: "Window") -> None: + def after_window_finished(self, window: Window) -> None: """ Provide a window hook after creating the window. @@ -248,7 +250,7 @@ def after_window_finished(self, window: "Window") -> None: window to hook into """ - def before_script(self, session: "Session") -> None: + def before_script(self, session: Session) -> None: """ Provide a session hook after the workspace has been built. @@ -276,7 +278,7 @@ def before_script(self, session: "Session") -> None: session to hook into """ - def reattach(self, session: "Session") -> None: + def reattach(self, session: Session) -> None: """ Provide a session hook before reattaching to the session. diff --git a/src/tmuxp/shell.py b/src/tmuxp/shell.py index bae2a203fa..78f9857ca2 100644 --- a/src/tmuxp/shell.py +++ b/src/tmuxp/shell.py @@ -1,15 +1,17 @@ # mypy: allow-untyped-calls """Utility and helper methods for tmuxp.""" +from __future__ import annotations + import logging import os import pathlib import typing as t -from collections.abc import Callable logger = logging.getLogger(__name__) if t.TYPE_CHECKING: + from collections.abc import Callable from types import ModuleType from libtmux.pane import Pane @@ -31,10 +33,10 @@ class LaunchOptionalImports(TypedDict): """tmuxp shell optional imports.""" - server: NotRequired["Server"] - session: NotRequired["Session"] - window: NotRequired["Window"] - pane: NotRequired["Pane"] + server: NotRequired[Server] + session: NotRequired[Session] + window: NotRequired[Window] + pane: NotRequired[Pane] class LaunchImports(t.TypedDict): """tmuxp shell launch import mapping.""" @@ -44,10 +46,10 @@ class LaunchImports(t.TypedDict): Session: type[Session] Window: type[Window] Pane: type[Pane] - server: t.Optional["Server"] - session: t.Optional["Session"] - window: t.Optional["Window"] - pane: t.Optional["Pane"] + server: Server | None + session: Session | None + window: Window | None + pane: Pane | None def has_ipython() -> bool: @@ -100,7 +102,7 @@ def has_bpython() -> bool: return True -def detect_best_shell() -> "CLIShellLiteral": +def detect_best_shell() -> CLIShellLiteral: """Return the best, most feature-rich shell available.""" if has_ptipython(): return "ptipython" @@ -114,8 +116,8 @@ def detect_best_shell() -> "CLIShellLiteral": def get_bpython( - options: "LaunchOptionalImports", - extra_args: t.Optional[dict[str, t.Any]] = None, + options: LaunchOptionalImports, + extra_args: dict[str, t.Any] | None = None, ) -> Callable[[], None]: """Return bpython shell.""" if extra_args is None: @@ -140,7 +142,7 @@ def get_ipython_arguments() -> list[str]: def get_ipython( - options: "LaunchOptionalImports", + options: LaunchOptionalImports, **extra_args: dict[str, t.Any], ) -> t.Any: """Return ipython shell.""" @@ -168,7 +170,7 @@ def launch_ipython() -> None: return launch_ipython -def get_ptpython(options: "LaunchOptionalImports", vi_mode: bool = False) -> t.Any: +def get_ptpython(options: LaunchOptionalImports, vi_mode: bool = False) -> t.Any: """Return ptpython shell.""" try: from ptpython.repl import embed, run_config @@ -188,7 +190,7 @@ def launch_ptpython() -> None: return launch_ptpython -def get_ptipython(options: "LaunchOptionalImports", vi_mode: bool = False) -> t.Any: +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 @@ -214,7 +216,7 @@ def launch_ptipython() -> None: return launch_ptipython -def get_launch_args(**kwargs: "Unpack[LaunchOptionalImports]") -> "LaunchImports": +def get_launch_args(**kwargs: Unpack[LaunchOptionalImports]) -> LaunchImports: """Return tmuxp shell launch arguments, counting for overrides.""" import libtmux from libtmux.pane import Pane @@ -235,7 +237,7 @@ def get_launch_args(**kwargs: "Unpack[LaunchOptionalImports]") -> "LaunchImports } -def get_code(use_pythonrc: bool, imported_objects: "LaunchImports") -> t.Any: +def get_code(use_pythonrc: bool, imported_objects: LaunchImports) -> t.Any: """Launch basic python shell via :mod:`code`.""" import code @@ -290,10 +292,10 @@ def launch_code() -> None: def launch( - shell: t.Optional["CLIShellLiteral"] = "best", + shell: CLIShellLiteral | None = "best", use_pythonrc: bool = False, use_vi_mode: bool = False, - **kwargs: "Unpack[LaunchOptionalImports]", + **kwargs: Unpack[LaunchOptionalImports], ) -> None: """Launch interactive libtmux shell for tmuxp shell.""" # Also allowing passing shell='code' to force using code.interact diff --git a/src/tmuxp/types.py b/src/tmuxp/types.py index d94328dfb3..0c2e05cd27 100644 --- a/src/tmuxp/types.py +++ b/src/tmuxp/types.py @@ -7,6 +7,8 @@ .. _typeshed's: https://github.com/python/typeshed/blob/9687d5/stdlib/_typeshed/__init__.pyi#L98 """ # E501 +from __future__ import annotations + from typing import TYPE_CHECKING, Union if TYPE_CHECKING: diff --git a/src/tmuxp/util.py b/src/tmuxp/util.py index fe7a2a2b79..b81fdb6e7e 100644 --- a/src/tmuxp/util.py +++ b/src/tmuxp/util.py @@ -1,8 +1,9 @@ """Utility and helper methods for tmuxp.""" +from __future__ import annotations + import logging import os -import pathlib import shlex import subprocess import sys @@ -13,6 +14,8 @@ from . import exc if t.TYPE_CHECKING: + import pathlib + from libtmux.pane import Pane from libtmux.server import Server from libtmux.session import Session @@ -24,8 +27,8 @@ def run_before_script( - script_file: t.Union[str, pathlib.Path], - cwd: t.Optional[pathlib.Path] = None, + script_file: str | pathlib.Path, + cwd: pathlib.Path | None = None, ) -> int: """Execute a shell script, wraps :meth:`subprocess.check_call()` in a try/catch.""" try: @@ -85,7 +88,7 @@ def oh_my_zsh_auto_title() -> None: ) -def get_current_pane(server: "Server") -> t.Optional["Pane"]: +def get_current_pane(server: Server) -> Pane | None: """Return Pane if one found in env.""" if os.getenv("TMUX_PANE") is not None: try: @@ -96,10 +99,10 @@ def get_current_pane(server: "Server") -> t.Optional["Pane"]: def get_session( - server: "Server", - session_name: t.Optional[str] = None, - current_pane: t.Optional["Pane"] = None, -) -> "Session": + server: Server, + session_name: str | None = None, + current_pane: Pane | None = None, +) -> Session: """Get tmux session for server by session name, respects current pane, if passed.""" try: if session_name: @@ -123,10 +126,10 @@ def get_session( def get_window( - session: "Session", - window_name: t.Optional[str] = None, - current_pane: t.Optional["Pane"] = None, -) -> "Window": + session: Session, + window_name: str | None = None, + current_pane: Pane | None = None, +) -> Window: """Get tmux window for server by window name, respects current pane, if passed.""" try: if window_name: @@ -146,7 +149,7 @@ def get_window( return window -def get_pane(window: "Window", current_pane: t.Optional["Pane"] = None) -> "Pane": +def get_pane(window: Window, current_pane: Pane | None = None) -> Pane: """Get tmux pane for server by pane name, respects current pane, if passed.""" pane = None try: diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index 3bcb78b8b8..e51ac7615d 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -1,11 +1,12 @@ """Create a tmux workspace from a workspace :py:obj:`dict`.""" +from __future__ import annotations + import logging import os import shutil import time import typing as t -from collections.abc import Iterator from libtmux._internal.query_list import ObjectDoesNotExist from libtmux.common import has_gte_version, has_lt_version @@ -17,6 +18,9 @@ from tmuxp import exc from tmuxp.util import get_current_pane, run_before_script +if t.TYPE_CHECKING: + from collections.abc import Iterator + logger = logging.getLogger(__name__) COLUMNS_FALLBACK = 80 @@ -144,15 +148,15 @@ class WorkspaceBuilder: a session inside tmux (when `$TMUX` is in the env variables). """ - server: "Server" - _session: t.Optional["Session"] + server: Server + _session: Session | None session_name: str def __init__( self, session_config: dict[str, t.Any], server: Server, - plugins: t.Optional[list[t.Any]] = None, + plugins: list[t.Any] | None = None, ) -> None: """Initialize workspace loading. @@ -219,7 +223,7 @@ def session_exists(self, session_name: str) -> bool: return False return True - def build(self, session: t.Optional[Session] = None, append: bool = False) -> None: + def build(self, session: Session | None = None, append: bool = False) -> None: """Build tmux workspace in session. Optionally accepts ``session`` to build with only session object. @@ -482,7 +486,7 @@ def iter_create_panes( def get_pane_start_directory( pane_config: dict[str, str], window_config: dict[str, str], - ) -> t.Optional[str]: + ) -> str | None: if "start_directory" in pane_config: return pane_config["start_directory"] if "start_directory" in window_config: @@ -492,7 +496,7 @@ def get_pane_start_directory( def get_pane_shell( pane_config: dict[str, str], window_config: dict[str, str], - ) -> t.Optional[str]: + ) -> str | None: if "shell" in pane_config: return pane_config["shell"] if "window_shell" in window_config: diff --git a/src/tmuxp/workspace/constants.py b/src/tmuxp/workspace/constants.py index 48c2d18ba4..48ecc9c1f6 100644 --- a/src/tmuxp/workspace/constants.py +++ b/src/tmuxp/workspace/constants.py @@ -1,3 +1,5 @@ """Constant variables for tmuxp workspace functionality.""" +from __future__ import annotations + VALID_WORKSPACE_DIR_FILE_EXTENSIONS = [".yaml", ".yml", ".json"] diff --git a/src/tmuxp/workspace/finders.py b/src/tmuxp/workspace/finders.py index 2c202bb0fe..c657d389dd 100644 --- a/src/tmuxp/workspace/finders.py +++ b/src/tmuxp/workspace/finders.py @@ -1,27 +1,31 @@ """Workspace (configuration file) finders for tmuxp.""" +from __future__ import annotations + import logging import os -import pathlib import typing as t from colorama import Fore from tmuxp.cli.utils import tmuxp_echo -from tmuxp.types import StrPath from tmuxp.workspace.constants import VALID_WORKSPACE_DIR_FILE_EXTENSIONS logger = logging.getLogger(__name__) if t.TYPE_CHECKING: + import pathlib + from typing_extensions import TypeAlias + from tmuxp.types import StrPath + ValidExtensions: TypeAlias = t.Literal[".yml", ".yaml", ".json"] def is_workspace_file( filename: str, - extensions: t.Union["ValidExtensions", list["ValidExtensions"], None] = None, + extensions: ValidExtensions | list[ValidExtensions] | None = None, ) -> bool: """ Return True if file has a valid workspace file type. @@ -44,8 +48,8 @@ def is_workspace_file( def in_dir( - workspace_dir: t.Union[pathlib.Path, str, None] = None, - extensions: t.Optional[list["ValidExtensions"]] = None, + workspace_dir: pathlib.Path | str | None = None, + extensions: list[ValidExtensions] | None = None, ) -> list[str]: """ Return a list of workspace_files in ``workspace_dir``. @@ -130,7 +134,7 @@ def get_workspace_dir() -> str: def find_workspace_file( workspace_file: StrPath, - workspace_dir: t.Optional[StrPath] = None, + workspace_dir: StrPath | None = None, ) -> str: """ Return the real config path or raise an exception. diff --git a/src/tmuxp/workspace/freezer.py b/src/tmuxp/workspace/freezer.py index fba5103a73..648a7755f1 100644 --- a/src/tmuxp/workspace/freezer.py +++ b/src/tmuxp/workspace/freezer.py @@ -1,11 +1,12 @@ """Tmux session freezing functionality for tmuxp.""" -import typing as t +from __future__ import annotations -from libtmux.pane import Pane -from libtmux.session import Session +import typing as t if t.TYPE_CHECKING: + from libtmux.pane import Pane + from libtmux.session import Session from libtmux.window import Window @@ -81,14 +82,14 @@ def freeze(session: Session) -> dict[str, t.Any]: # If all panes have same path, set 'start_directory' instead # of using 'cd' shell commands. - def pane_has_same_path(window: "Window", pane: Pane) -> bool: + def pane_has_same_path(window: Window, pane: Pane) -> bool: return window.panes[0].pane_current_path == pane.pane_current_path if all(pane_has_same_path(window=window, pane=pane) for pane in window.panes): window_config["start_directory"] = window.panes[0].pane_current_path for pane in window.panes: - pane_config: t.Union[str, dict[str, t.Any]] = {"shell_command": []} + pane_config: str | dict[str, t.Any] = {"shell_command": []} assert isinstance(pane_config, dict) if "start_directory" not in window_config and pane.pane_current_path: @@ -99,7 +100,7 @@ def pane_has_same_path(window: "Window", pane: Pane) -> bool: current_cmd = pane.pane_current_command - def filter_interpreters_and_shells(current_cmd: t.Optional[str]) -> bool: + def filter_interpreters_and_shells(current_cmd: str | None) -> bool: return current_cmd is not None and ( current_cmd.startswith("-") or any( diff --git a/src/tmuxp/workspace/importers.py b/src/tmuxp/workspace/importers.py index 2ee9c7880f..7a4c189b70 100644 --- a/src/tmuxp/workspace/importers.py +++ b/src/tmuxp/workspace/importers.py @@ -1,5 +1,7 @@ """Configuration import adapters to load teamocil, tmuxinator, etc. in tmuxp.""" +from __future__ import annotations + import typing as t diff --git a/src/tmuxp/workspace/loader.py b/src/tmuxp/workspace/loader.py index bf9c7c098a..613b2b589d 100644 --- a/src/tmuxp/workspace/loader.py +++ b/src/tmuxp/workspace/loader.py @@ -1,5 +1,7 @@ """Workspace hydration and loading for tmuxp.""" +from __future__ import annotations + import logging import os import pathlib @@ -65,8 +67,8 @@ def expand_cmd(p: dict[str, t.Any]) -> dict[str, t.Any]: def expand( workspace_dict: dict[str, t.Any], - cwd: t.Optional[t.Union[pathlib.Path, str]] = None, - parent: t.Optional[t.Any] = None, + cwd: pathlib.Path | str | None = None, + parent: t.Any | None = None, ) -> dict[str, t.Any]: """Resolve workspace variables and expand shorthand style / inline properties. diff --git a/src/tmuxp/workspace/validation.py b/src/tmuxp/workspace/validation.py index b200bd6308..fd549856ec 100644 --- a/src/tmuxp/workspace/validation.py +++ b/src/tmuxp/workspace/validation.py @@ -1,5 +1,7 @@ """Validation errors for tmuxp configuration files.""" +from __future__ import annotations + import typing as t from tmuxp import exc diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 2c271d39e2..14e0b30da0 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -1,5 +1,7 @@ """CLI tests for tmuxp's core shell functionality.""" +from __future__ import annotations + import argparse import contextlib import pathlib @@ -112,7 +114,7 @@ def test_pass_config_dir_argparse( def config_cmd(workspace_file: str) -> None: tmuxp_echo(find_workspace_file(workspace_file, workspace_dir=configdir)) - def check_cmd(config_arg: str) -> "_pytest.capture.CaptureResult[str]": + 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() @@ -130,7 +132,7 @@ def check_cmd(config_arg: str) -> "_pytest.capture.CaptureResult[str]": def test_reattach_plugins( monkeypatch_plugin_test_packages: None, - server: "Server", + server: Server, ) -> None: """Test reattach plugin hook.""" config_plugins = test_utils.read_workspace_file("workspace/builder/plugin_r.yaml") diff --git a/tests/cli/test_convert.py b/tests/cli/test_convert.py index d9de79ede8..6946040948 100644 --- a/tests/cli/test_convert.py +++ b/tests/cli/test_convert.py @@ -1,14 +1,19 @@ """CLI tests for tmuxp convert.""" +from __future__ import annotations + import contextlib import io import json -import pathlib +from typing import TYPE_CHECKING import pytest from tmuxp import cli +if TYPE_CHECKING: + import pathlib + @pytest.mark.parametrize( "cli_args", diff --git a/tests/cli/test_debug_info.py b/tests/cli/test_debug_info.py index 8bebb3e9cf..5275fdaefe 100644 --- a/tests/cli/test_debug_info.py +++ b/tests/cli/test_debug_info.py @@ -1,11 +1,16 @@ """CLI tests for tmuxp debuginfo.""" -import pathlib +from __future__ import annotations -import pytest +from typing import TYPE_CHECKING from tmuxp import cli +if TYPE_CHECKING: + import pathlib + + import pytest + def test_debug_info_cli( monkeypatch: pytest.MonkeyPatch, diff --git a/tests/cli/test_freeze.py b/tests/cli/test_freeze.py index 0d2d184ba6..ef3a15812e 100644 --- a/tests/cli/test_freeze.py +++ b/tests/cli/test_freeze.py @@ -1,8 +1,9 @@ """Test workspace freezing functionality for tmuxp.""" +from __future__ import annotations + import contextlib import io -import pathlib import typing as t import pytest @@ -11,6 +12,8 @@ from tmuxp._internal.config_reader import ConfigReader if t.TYPE_CHECKING: + import pathlib + from libtmux.server import Server @@ -30,7 +33,7 @@ ], ) def test_freeze( - server: "Server", + server: Server, cli_args: list[str], inputs: list[str], tmp_path: pathlib.Path, @@ -82,7 +85,7 @@ def test_freeze( ], ) def test_freeze_overwrite( - server: "Server", + server: Server, cli_args: list[str], inputs: list[str], tmp_path: pathlib.Path, diff --git a/tests/cli/test_import.py b/tests/cli/test_import.py index 2e1e1497f9..8dc8c9ec96 100644 --- a/tests/cli/test_import.py +++ b/tests/cli/test_import.py @@ -1,14 +1,19 @@ """CLI tests for tmuxp import.""" +from __future__ import annotations + import contextlib import io -import pathlib +from typing import TYPE_CHECKING import pytest from tests.fixtures import utils as test_utils from tmuxp import cli +if TYPE_CHECKING: + import pathlib + @pytest.mark.parametrize("cli_args", [(["import"])]) def test_import( diff --git a/tests/cli/test_load.py b/tests/cli/test_load.py index fd04f48762..95925a1dd9 100644 --- a/tests/cli/test_load.py +++ b/tests/cli/test_load.py @@ -1,5 +1,7 @@ """CLI tests for tmuxp load.""" +from __future__ import annotations + import contextlib import io import pathlib @@ -10,7 +12,6 @@ from libtmux.common import has_lt_version from libtmux.server import Server from libtmux.session import Session -from pytest_mock import MockerFixture from tests.constants import FIXTURE_PATH from tests.fixtures import utils as test_utils @@ -27,7 +28,7 @@ def test_load_workspace( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, ) -> None: """Generic test for loading a tmuxp workspace via tmuxp load.""" @@ -49,7 +50,7 @@ def test_load_workspace( def test_load_workspace_passes_tmux_config( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, ) -> None: """Test tmuxp load with a tmux configuration file.""" @@ -73,7 +74,7 @@ def test_load_workspace_passes_tmux_config( def test_load_workspace_named_session( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, ) -> None: """Test tmuxp load with a custom tmux session name.""" @@ -101,7 +102,7 @@ def test_load_workspace_named_session( ) def test_load_workspace_name_match_regression_252( tmp_path: pathlib.Path, - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, ) -> None: """Test tmuxp load for a regression where tmux shell names would not match.""" @@ -141,7 +142,7 @@ def test_load_workspace_name_match_regression_252( def test_load_symlinked_workspace( - server: "Server", + server: Server, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -185,6 +186,7 @@ def test_load_symlinked_workspace( if t.TYPE_CHECKING: + from pytest_mock import MockerFixture from typing_extensions import TypeAlias ExpectedOutput: TypeAlias = t.Optional[t.Union[str, list[str]]] @@ -197,14 +199,14 @@ class CLILoadFixture(t.NamedTuple): test_id: str # test params - cli_args: list[t.Union[str, list[str]]] + cli_args: list[str | list[str]] config_paths: list[str] session_names: list[str] expected_exit_code: int - expected_in_out: "ExpectedOutput" = None - expected_not_in_out: "ExpectedOutput" = None - expected_in_err: "ExpectedOutput" = None - expected_not_in_err: "ExpectedOutput" = None + expected_in_out: ExpectedOutput = None + expected_not_in_out: ExpectedOutput = None + expected_in_err: ExpectedOutput = None + expected_not_in_err: ExpectedOutput = None TEST_LOAD_FIXTURES: list[CLILoadFixture] = [ @@ -289,7 +291,7 @@ class CLILoadFixture(t.NamedTuple): def test_load( tmp_path: pathlib.Path, tmuxp_configdir: pathlib.Path, - server: "Server", + server: Server, session: Session, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, @@ -298,10 +300,10 @@ def test_load( config_paths: list[str], session_names: list[str], expected_exit_code: int, - expected_in_out: "ExpectedOutput", - expected_not_in_out: "ExpectedOutput", - expected_in_err: "ExpectedOutput", - expected_not_in_err: "ExpectedOutput", + expected_in_out: ExpectedOutput, + expected_not_in_out: ExpectedOutput, + expected_in_err: ExpectedOutput, + expected_not_in_err: ExpectedOutput, ) -> None: """Parametrized test battery for tmuxp load CLI command.""" assert server.socket_name is not None @@ -346,7 +348,7 @@ def test_load( def test_regression_00132_session_name_with_dots( tmp_path: pathlib.Path, - server: "Server", + server: Server, session: Session, capsys: pytest.CaptureFixture[str], ) -> None: @@ -366,7 +368,7 @@ def test_load_zsh_autotitle_warning( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], - server: "Server", + server: Server, ) -> None: """Test loading ZSH without DISABLE_AUTO_TITLE raises warning.""" # create dummy tmuxp yaml so we don't get yelled at @@ -548,7 +550,7 @@ def test_load_plugins_plugin_missing( def test_plugin_system_before_script( monkeypatch_plugin_test_packages: None, - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, ) -> None: """Test tmuxp load with sessions using before_script.""" @@ -570,7 +572,7 @@ def test_plugin_system_before_script( def test_load_attached( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, ) -> None: @@ -592,7 +594,7 @@ def test_load_attached( def test_load_attached_detached( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, ) -> None: @@ -614,7 +616,7 @@ def test_load_attached_detached( def test_load_attached_within_tmux( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, ) -> None: @@ -636,7 +638,7 @@ def test_load_attached_within_tmux( def test_load_attached_within_tmux_detached( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, ) -> None: @@ -658,7 +660,7 @@ def test_load_attached_within_tmux_detached( def test_load_append_windows_to_current_session( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, ) -> None: """Test tmuxp load when windows are appended to the current session.""" diff --git a/tests/cli/test_ls.py b/tests/cli/test_ls.py index 9532dd3f7c..6f3a0da907 100644 --- a/tests/cli/test_ls.py +++ b/tests/cli/test_ls.py @@ -1,12 +1,16 @@ """CLI tests for tmuxp ls command.""" +from __future__ import annotations + import contextlib import pathlib - -import pytest +from typing import TYPE_CHECKING from tmuxp import cli +if TYPE_CHECKING: + import pytest + def test_ls_cli( monkeypatch: pytest.MonkeyPatch, diff --git a/tests/cli/test_shell.py b/tests/cli/test_shell.py index 6690b2a333..65ad67b2ef 100644 --- a/tests/cli/test_shell.py +++ b/tests/cli/test_shell.py @@ -1,18 +1,21 @@ """CLI tests for tmuxp shell.""" +from __future__ import annotations + import contextlib import io -import pathlib import subprocess import typing as t import pytest -from libtmux.session import Session from tmuxp import cli, exc if t.TYPE_CHECKING: + import pathlib + from libtmux.server import Server + from libtmux.session import Session class CLIShellFixture(t.NamedTuple): @@ -114,7 +117,7 @@ def test_shell( inputs: list[t.Any], env: dict[str, str], expected_output: str, - server: "Server", + server: Server, session: Session, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, @@ -201,13 +204,10 @@ def test_shell_target_missing( inputs: list[t.Any], env: dict[t.Any, t.Any], template_ctx: dict[str, str], - exception: t.Union[ - type[exc.TmuxpException], - type[subprocess.CalledProcessError], - ], + exception: type[exc.TmuxpException | subprocess.CalledProcessError], message: str, socket_name: str, - server: "Server", + server: Server, session: Session, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, @@ -284,7 +284,7 @@ def test_shell_interactive( inputs: list[t.Any], env: dict[str, str], message: str, - server: "Server", + server: Server, session: Session, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, diff --git a/tests/constants.py b/tests/constants.py index ff3cc8bd0f..ee67d4c770 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -1,5 +1,7 @@ """Constant variables for tmuxp tests.""" +from __future__ import annotations + import pathlib TESTS_PATH = pathlib.Path(__file__).parent diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index fd26c7acab..2f0841acd8 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -1,3 +1,5 @@ """Fixture test data for tmuxp.""" +from __future__ import annotations + from . import utils diff --git a/tests/fixtures/import_teamocil/__init__.py b/tests/fixtures/import_teamocil/__init__.py index cb19a6253b..1ec7c59fd5 100644 --- a/tests/fixtures/import_teamocil/__init__.py +++ b/tests/fixtures/import_teamocil/__init__.py @@ -1,3 +1,5 @@ """Teamocil data fixtures for import_teamocil tests.""" +from __future__ import annotations + from . import layouts, test1, test2, test3, test4 diff --git a/tests/fixtures/import_teamocil/layouts.py b/tests/fixtures/import_teamocil/layouts.py index d09e0fbf0a..6e3e06bf7f 100644 --- a/tests/fixtures/import_teamocil/layouts.py +++ b/tests/fixtures/import_teamocil/layouts.py @@ -1,5 +1,7 @@ """Teamocil data fixtures for import_teamocil tests, for layout testing.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils teamocil_yaml_file = test_utils.get_workspace_file("import_teamocil/layouts.yaml") diff --git a/tests/fixtures/import_teamocil/test1.py b/tests/fixtures/import_teamocil/test1.py index 3824626876..8e2065fec2 100644 --- a/tests/fixtures/import_teamocil/test1.py +++ b/tests/fixtures/import_teamocil/test1.py @@ -1,5 +1,7 @@ """Teamocil data fixtures for import_teamocil tests, 1st test.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test1.yaml") diff --git a/tests/fixtures/import_teamocil/test2.py b/tests/fixtures/import_teamocil/test2.py index ad6986880f..0353a0edbf 100644 --- a/tests/fixtures/import_teamocil/test2.py +++ b/tests/fixtures/import_teamocil/test2.py @@ -1,5 +1,7 @@ """Teamocil data fixtures for import_teamocil tests, 2nd test.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test2.yaml") diff --git a/tests/fixtures/import_teamocil/test3.py b/tests/fixtures/import_teamocil/test3.py index 5b2687c1c4..1bcd32fef1 100644 --- a/tests/fixtures/import_teamocil/test3.py +++ b/tests/fixtures/import_teamocil/test3.py @@ -1,5 +1,7 @@ """Teamocil data fixtures for import_teamocil tests, 3rd test.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test3.yaml") diff --git a/tests/fixtures/import_teamocil/test4.py b/tests/fixtures/import_teamocil/test4.py index b7883651b3..1837abf508 100644 --- a/tests/fixtures/import_teamocil/test4.py +++ b/tests/fixtures/import_teamocil/test4.py @@ -1,5 +1,7 @@ """Teamocil data fixtures for import_teamocil tests, 4th test.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test4.yaml") diff --git a/tests/fixtures/import_tmuxinator/__init__.py b/tests/fixtures/import_tmuxinator/__init__.py index 1ff1cafde3..84508e0405 100644 --- a/tests/fixtures/import_tmuxinator/__init__.py +++ b/tests/fixtures/import_tmuxinator/__init__.py @@ -1,3 +1,5 @@ """Tmuxinator data fixtures for import_tmuxinator tests.""" +from __future__ import annotations + from . import test1, test2, test3 diff --git a/tests/fixtures/import_tmuxinator/test1.py b/tests/fixtures/import_tmuxinator/test1.py index 03fbc4196f..7e08f976d0 100644 --- a/tests/fixtures/import_tmuxinator/test1.py +++ b/tests/fixtures/import_tmuxinator/test1.py @@ -1,5 +1,7 @@ """Tmuxinator data fixtures for import_tmuxinator tests, 1st dataset.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test1.yaml") diff --git a/tests/fixtures/import_tmuxinator/test2.py b/tests/fixtures/import_tmuxinator/test2.py index 12fef7754a..97d923a912 100644 --- a/tests/fixtures/import_tmuxinator/test2.py +++ b/tests/fixtures/import_tmuxinator/test2.py @@ -1,5 +1,7 @@ """Tmuxinator data fixtures for import_tmuxinator tests, 2nd dataset.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test2.yaml") diff --git a/tests/fixtures/import_tmuxinator/test3.py b/tests/fixtures/import_tmuxinator/test3.py index ed479c57b5..86ebd22c16 100644 --- a/tests/fixtures/import_tmuxinator/test3.py +++ b/tests/fixtures/import_tmuxinator/test3.py @@ -1,5 +1,7 @@ """Tmuxinator data fixtures for import_tmuxinator tests, 3rd dataset.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test3.yaml") diff --git a/tests/fixtures/pluginsystem/partials/_types.py b/tests/fixtures/pluginsystem/partials/_types.py index 77d2328cd5..0809596bdf 100644 --- a/tests/fixtures/pluginsystem/partials/_types.py +++ b/tests/fixtures/pluginsystem/partials/_types.py @@ -10,6 +10,8 @@ ... """ +from __future__ import annotations + from typing_extensions import NotRequired, TypedDict diff --git a/tests/fixtures/pluginsystem/partials/all_pass.py b/tests/fixtures/pluginsystem/partials/all_pass.py index 357cfff494..293d70bef1 100644 --- a/tests/fixtures/pluginsystem/partials/all_pass.py +++ b/tests/fixtures/pluginsystem/partials/all_pass.py @@ -1,5 +1,7 @@ """Tmuxp test plugin with version constraints guaranteed to pass.""" +from __future__ import annotations + import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin diff --git a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py index 85a1e51184..e69efaa3f0 100644 --- a/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/libtmux_version_fail.py @@ -1,5 +1,7 @@ """Fixtures for tmuxp plugins for libtmux version exceptions.""" +from __future__ import annotations + import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin diff --git a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py index 5c27dce976..d73674cf3f 100644 --- a/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py +++ b/tests/fixtures/pluginsystem/partials/test_plugin_helpers.py @@ -1,5 +1,7 @@ """Tmuxp test plugin for asserting version constraints.""" +from __future__ import annotations + import typing as t from tmuxp.plugin import TmuxpPlugin @@ -15,7 +17,7 @@ class MyTestTmuxpPlugin(TmuxpPlugin): """Base class for testing tmuxp plugins with version constraints.""" - def __init__(self, **config: "Unpack[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) diff --git a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py index 6a91f6d960..deaa89d2cf 100644 --- a/tests/fixtures/pluginsystem/partials/tmux_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmux_version_fail.py @@ -1,5 +1,7 @@ """Fixtures for tmuxp plugins for tmux version exceptions.""" +from __future__ import annotations + import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin diff --git a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py index 703e7b3d25..aa5b8e7783 100644 --- a/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py +++ b/tests/fixtures/pluginsystem/partials/tmuxp_version_fail.py @@ -1,5 +1,7 @@ """Fixtures for tmuxp plugins for tmuxp version exceptions.""" +from __future__ import annotations + import typing as t from .test_plugin_helpers import MyTestTmuxpPlugin 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 edab6ed686..fe4a604ab1 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,5 +1,7 @@ """Tmuxp example plugin for after_window_finished.""" +from __future__ import annotations + import typing as t from tmuxp.plugin import TmuxpPlugin @@ -14,7 +16,7 @@ class PluginAfterWindowFinished(TmuxpPlugin): def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" - def after_window_finished(self, window: "Window") -> None: + def after_window_finished(self, window: Window) -> None: """Run hook after window creation completed.""" if window.name == "editor": window.rename_window("plugin_test_awf") 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 6da3e3ae2b..9bc64c9e37 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,5 +1,7 @@ """Tmux plugin that runs before_script, if it is declared in configuration.""" +from __future__ import annotations + import typing as t from tmuxp.plugin import TmuxpPlugin @@ -14,6 +16,6 @@ class PluginBeforeScript(TmuxpPlugin): def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" - def before_script(self, session: "Session") -> None: + def before_script(self, session: Session) -> None: """Run hook during before_script, if it is declared.""" 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 fba2134e15..b564ec5829 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,5 +1,7 @@ """Tmuxp example plugin for before_worksplace_builder.""" +from __future__ import annotations + import typing as t from tmuxp.plugin import TmuxpPlugin @@ -14,6 +16,6 @@ class PluginBeforeWorkspaceBuilder(TmuxpPlugin): def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" - def before_workspace_builder(self, session: "Session") -> None: + def before_workspace_builder(self, session: Session) -> None: """Run hook before workspace builder begins.""" 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 520cf55c93..9be75a6e93 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,5 +1,7 @@ """Tmuxp example plugin that fails on initialization.""" +from __future__ import annotations + import typing as t from tmuxp.plugin import TmuxpPlugin 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 8c330a648c..d21006ce99 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,5 +1,7 @@ """Tmuxp example plugin for on_window_create.""" +from __future__ import annotations + import typing as t from tmuxp.plugin import TmuxpPlugin @@ -14,7 +16,7 @@ class PluginOnWindowCreate(TmuxpPlugin): def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" - def on_window_create(self, window: "Window") -> None: + def on_window_create(self, window: Window) -> None: """Apply hook that runs for tmux on session reattach.""" if window.name == "editor": window.rename_window("plugin_test_owc") 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 373e9e9580..396afabf5a 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,5 +1,7 @@ """Tmuxp example plugin for reattaching session.""" +from __future__ import annotations + import typing as t from tmuxp.plugin import TmuxpPlugin @@ -14,6 +16,6 @@ class PluginReattach(TmuxpPlugin): def __init__(self) -> None: self.message: str = "[+] This is the Tmuxp Test Plugin" - def reattach(self, session: "Session") -> None: + def reattach(self, session: Session) -> None: """Apply hook that runs for tmux on session reattach.""" session.rename_session("plugin_test_r") diff --git a/tests/fixtures/structures.py b/tests/fixtures/structures.py index 4e6ca17c03..5c7587e4ce 100644 --- a/tests/fixtures/structures.py +++ b/tests/fixtures/structures.py @@ -1,5 +1,7 @@ """Typings / structures for tmuxp fixtures.""" +from __future__ import annotations + import dataclasses import typing as t diff --git a/tests/fixtures/utils.py b/tests/fixtures/utils.py index 81fd07cf89..23c4ac26fd 100644 --- a/tests/fixtures/utils.py +++ b/tests/fixtures/utils.py @@ -1,13 +1,14 @@ """Utility functions for tmuxp fixtures.""" +from __future__ import annotations + import pathlib -import typing as t from tests.constants import FIXTURE_PATH def get_workspace_file( - file: t.Union[str, pathlib.Path], + file: str | pathlib.Path, ) -> pathlib.Path: """Return fixture data, relative to __file__.""" if isinstance(file, str): @@ -17,7 +18,7 @@ def get_workspace_file( def read_workspace_file( - file: t.Union[pathlib.Path, str], + file: pathlib.Path | str, ) -> str: """Return fixture data, relative to __file__.""" if isinstance(file, str): diff --git a/tests/fixtures/workspace/__init__.py b/tests/fixtures/workspace/__init__.py index e80a60fabd..b097ef9cb6 100644 --- a/tests/fixtures/workspace/__init__.py +++ b/tests/fixtures/workspace/__init__.py @@ -1,5 +1,7 @@ """Workspace data fixtures for tmuxp tests.""" +from __future__ import annotations + from . import ( expand1, expand2, diff --git a/tests/fixtures/workspace/expand1.py b/tests/fixtures/workspace/expand1.py index 75d5d06c00..6ad0cbb9e3 100644 --- a/tests/fixtures/workspace/expand1.py +++ b/tests/fixtures/workspace/expand1.py @@ -1,5 +1,7 @@ """Examples of expansion of tmuxp configurations from shorthand style.""" +from __future__ import annotations + import pathlib import typing as t diff --git a/tests/fixtures/workspace/expand2.py b/tests/fixtures/workspace/expand2.py index 3acdc3f387..7e8089bbbc 100644 --- a/tests/fixtures/workspace/expand2.py +++ b/tests/fixtures/workspace/expand2.py @@ -1,5 +1,7 @@ """YAML examples of expansion of tmuxp configurations from shorthand style.""" +from __future__ import annotations + import pathlib from tests.fixtures import utils as test_utils diff --git a/tests/fixtures/workspace/expand_blank.py b/tests/fixtures/workspace/expand_blank.py index a9bbc70e51..5137605297 100644 --- a/tests/fixtures/workspace/expand_blank.py +++ b/tests/fixtures/workspace/expand_blank.py @@ -1,5 +1,7 @@ """Expected expanded configuration for empty workspace panes.""" +from __future__ import annotations + expected = { "session_name": "Blank pane test", "windows": [ diff --git a/tests/fixtures/workspace/sample_workspace.py b/tests/fixtures/workspace/sample_workspace.py index e3bf60347b..e00b15113a 100644 --- a/tests/fixtures/workspace/sample_workspace.py +++ b/tests/fixtures/workspace/sample_workspace.py @@ -1,5 +1,7 @@ """Example workspace fixture for tmuxp WorkspaceBuilder.""" +from __future__ import annotations + sample_workspace_dict = { "session_name": "sample workspace", "start_directory": "~", diff --git a/tests/fixtures/workspace/shell_command_before.py b/tests/fixtures/workspace/shell_command_before.py index 10b74518dd..53f8e1587f 100644 --- a/tests/fixtures/workspace/shell_command_before.py +++ b/tests/fixtures/workspace/shell_command_before.py @@ -1,5 +1,7 @@ """Test fixture for tmuxp to demonstrate shell_command_before.""" +from __future__ import annotations + import pathlib import typing as t diff --git a/tests/fixtures/workspace/shell_command_before_session.py b/tests/fixtures/workspace/shell_command_before_session.py index f3639ded0c..adfc86f183 100644 --- a/tests/fixtures/workspace/shell_command_before_session.py +++ b/tests/fixtures/workspace/shell_command_before_session.py @@ -1,5 +1,7 @@ """Tests shell_command_before configuration.""" +from __future__ import annotations + from tests.fixtures import utils as test_utils before = test_utils.read_workspace_file("workspace/shell_command_before_session.yaml") diff --git a/tests/fixtures/workspace/trickle.py b/tests/fixtures/workspace/trickle.py index afb138081e..a1012be647 100644 --- a/tests/fixtures/workspace/trickle.py +++ b/tests/fixtures/workspace/trickle.py @@ -1,5 +1,7 @@ """Test data for tmuxp workspace fixture to demo object tree inheritance.""" +from __future__ import annotations + before = { # shell_command_before is string in some areas "session_name": "sample workspace", "start_directory": "/var", diff --git a/tests/test_plugin.py b/tests/test_plugin.py index feee07d375..cf7cfc6371 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,5 +1,7 @@ """Tests for tmuxp plugin API.""" +from __future__ import annotations + import pytest from tmuxp.exc import TmuxpPluginException diff --git a/tests/test_shell.py b/tests/test_shell.py index c8cf67b8f9..d544439d60 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -1,5 +1,7 @@ """Tests for tmuxp shell module.""" +from __future__ import annotations + from tmuxp import shell diff --git a/tests/test_util.py b/tests/test_util.py index 04e2940688..12a426f92c 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,7 +1,10 @@ """Tests for tmuxp's utility functions.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import pytest -from libtmux.server import Server from tmuxp import exc from tmuxp.exc import BeforeLoadScriptError, BeforeLoadScriptNotExists @@ -9,6 +12,9 @@ from .constants import FIXTURE_PATH +if TYPE_CHECKING: + from libtmux.server import Server + def test_run_before_script_raise_BeforeLoadScriptNotExists_if_not_exists() -> None: """run_before_script() raises BeforeLoadScriptNotExists if script not found.""" diff --git a/tests/tests/test_helpers.py b/tests/tests/test_helpers.py index 1b9658ba81..53088d4614 100644 --- a/tests/tests/test_helpers.py +++ b/tests/tests/test_helpers.py @@ -1,9 +1,15 @@ """Tests for tmuxp's helper and utility functions.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import pytest -from libtmux.server import Server from libtmux.test import get_test_session_name, temp_session +if TYPE_CHECKING: + from libtmux.server import Server + def test_temp_session_kills_session_on_exit(server: Server) -> None: """Test temp_session() context manager kills session on exit.""" diff --git a/tests/workspace/conftest.py b/tests/workspace/conftest.py index b3549b8c13..403189710a 100644 --- a/tests/workspace/conftest.py +++ b/tests/workspace/conftest.py @@ -1,5 +1,7 @@ """Pytest configuration for tmuxp workspace tests.""" +from __future__ import annotations + import types import pytest diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 9640cd6e31..8c5f00e2d2 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -1,5 +1,7 @@ """Test for tmuxp workspace builder.""" +from __future__ import annotations + import functools import os import pathlib @@ -13,7 +15,6 @@ 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 @@ -27,6 +28,7 @@ from tmuxp.workspace.builder import WorkspaceBuilder if t.TYPE_CHECKING: + from libtmux.server import Server class AssertCallbackProtocol(t.Protocol): """Assertion callback type protocol.""" @@ -481,7 +483,7 @@ def test_environment_variables_warns_prior_to_tmux_3_0( def test_automatic_rename_option( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, ) -> None: """Test workspace builder with automatic renaming enabled.""" @@ -1461,7 +1463,7 @@ class DefaultSizeNamespaceFixture(t.NamedTuple): test_id: str # test params - TMUXP_DEFAULT_SIZE: t.Optional[str] + TMUXP_DEFAULT_SIZE: str | None raises: bool confoverrides: dict[str, t.Any] @@ -1495,10 +1497,10 @@ class DefaultSizeNamespaceFixture(t.NamedTuple): ) @pytest.mark.skipif(has_lt_version("2.9"), reason="default-size only applies there") def test_issue_800_default_size_many_windows( - server: "Server", + server: Server, monkeypatch: pytest.MonkeyPatch, test_id: str, - TMUXP_DEFAULT_SIZE: t.Optional[str], + TMUXP_DEFAULT_SIZE: str | None, raises: bool, confoverrides: dict[str, t.Any], ) -> None: diff --git a/tests/workspace/test_config.py b/tests/workspace/test_config.py index f04cd1d93b..02ebcf5ffa 100644 --- a/tests/workspace/test_config.py +++ b/tests/workspace/test_config.py @@ -1,5 +1,7 @@ """Test for tmuxp configuration import, inlining, expanding and export.""" +from __future__ import annotations + import pathlib import typing as t @@ -14,7 +16,7 @@ from tests.fixtures.structures import WorkspaceTestData -def load_workspace(path: t.Union[str, pathlib.Path]) -> dict[str, t.Any]: +def load_workspace(path: str | pathlib.Path) -> dict[str, t.Any]: """Load tmuxp workspace configuration from file.""" return ConfigReader._from_file( pathlib.Path(path) if isinstance(path, str) else path, @@ -23,7 +25,7 @@ def load_workspace(path: t.Union[str, pathlib.Path]) -> dict[str, t.Any]: def test_export_json( tmp_path: pathlib.Path, - config_fixture: "WorkspaceTestData", + config_fixture: WorkspaceTestData, ) -> None: """Test exporting configuration dictionary to JSON.""" json_workspace_file = tmp_path / "config.json" @@ -38,13 +40,13 @@ def test_export_json( assert config_fixture.sample_workspace.sample_workspace_dict == new_workspace_data -def test_workspace_expand1(config_fixture: "WorkspaceTestData") -> None: +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") -> None: +def test_workspace_expand2(config_fixture: WorkspaceTestData) -> None: """Expand shell commands from string to list.""" unexpanded_dict = ConfigReader._load( fmt="yaml", @@ -127,7 +129,7 @@ def test_inheritance_workspace() -> None: assert workspace == inheritance_workspace_after -def test_shell_command_before(config_fixture: "WorkspaceTestData") -> None: +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) @@ -138,7 +140,7 @@ def test_shell_command_before(config_fixture: "WorkspaceTestData") -> None: assert test_workspace == config_fixture.shell_command_before.config_after() -def test_in_session_scope(config_fixture: "WorkspaceTestData") -> None: +def test_in_session_scope(config_fixture: WorkspaceTestData) -> None: """Verify shell_command before_session is in session scope.""" sconfig = ConfigReader._load( fmt="yaml", @@ -154,7 +156,7 @@ def test_in_session_scope(config_fixture: "WorkspaceTestData") -> None: ) -def test_trickle_relative_start_directory(config_fixture: "WorkspaceTestData") -> None: +def test_trickle_relative_start_directory(config_fixture: WorkspaceTestData) -> None: """Verify tmuxp config proliferates relative start directory to descendants.""" test_workspace = loader.trickle(config_fixture.trickle.before) assert test_workspace == config_fixture.trickle.expected @@ -179,7 +181,7 @@ def test_trickle_window_with_no_pane_workspace() -> None: } -def test_expands_blank_panes(config_fixture: "WorkspaceTestData") -> None: +def test_expands_blank_panes(config_fixture: WorkspaceTestData) -> None: """Expand blank config into full form. Handle ``NoneType`` and 'blank':: diff --git a/tests/workspace/test_finder.py b/tests/workspace/test_finder.py index 3496a2c049..862389e29c 100644 --- a/tests/workspace/test_finder.py +++ b/tests/workspace/test_finder.py @@ -1,5 +1,7 @@ """Test config file searching for tmuxp.""" +from __future__ import annotations + import argparse import pathlib import typing as t @@ -267,7 +269,7 @@ def config_cmd(workspace_file: str) -> None: project_config = projectdir / ".tmuxp.yaml" - def check_cmd(config_arg: str) -> "_pytest.capture.CaptureResult[str]": + 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() diff --git a/tests/workspace/test_freezer.py b/tests/workspace/test_freezer.py index c762e2212e..42fa6cc581 100644 --- a/tests/workspace/test_freezer.py +++ b/tests/workspace/test_freezer.py @@ -1,17 +1,20 @@ """Tests tmux session freezing functionality for tmuxp.""" -import pathlib +from __future__ import annotations + import time import typing -from libtmux.session import Session - from tests.fixtures import utils as test_utils from tmuxp._internal.config_reader import ConfigReader from tmuxp.workspace import freezer, validation from tmuxp.workspace.builder import WorkspaceBuilder if typing.TYPE_CHECKING: + import pathlib + + from libtmux.session import Session + from tests.fixtures.structures import WorkspaceTestData @@ -87,7 +90,7 @@ def test_inline_workspace() -> None: def test_export_yaml( tmp_path: pathlib.Path, - config_fixture: "WorkspaceTestData", + config_fixture: WorkspaceTestData, ) -> None: """Test exporting a frozen tmux session to YAML.""" yaml_workspace_file = tmp_path / "config.yaml" diff --git a/tests/workspace/test_import_teamocil.py b/tests/workspace/test_import_teamocil.py index 347fe26627..2f6ac9800f 100644 --- a/tests/workspace/test_import_teamocil.py +++ b/tests/workspace/test_import_teamocil.py @@ -1,5 +1,7 @@ """Test for tmuxp teamocil configuration.""" +from __future__ import annotations + import typing as t import pytest diff --git a/tests/workspace/test_import_tmuxinator.py b/tests/workspace/test_import_tmuxinator.py index 086c382f83..e6443e3204 100644 --- a/tests/workspace/test_import_tmuxinator.py +++ b/tests/workspace/test_import_tmuxinator.py @@ -1,5 +1,7 @@ """Test for tmuxp tmuxinator configuration.""" +from __future__ import annotations + import typing as t import pytest From dee96a79101659d103dd4a4820739583c06ae4fc Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 07:34:20 -0600 Subject: [PATCH 7/8] chore: Refine imports to use `t` namespace --- src/tmuxp/types.py | 6 +++--- tests/cli/test_convert.py | 4 ++-- tests/cli/test_debug_info.py | 4 ++-- tests/cli/test_import.py | 4 ++-- tests/cli/test_ls.py | 4 ++-- tests/test_util.py | 4 ++-- tests/tests/test_helpers.py | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/tmuxp/types.py b/src/tmuxp/types.py index 0c2e05cd27..4d267ae2ee 100644 --- a/src/tmuxp/types.py +++ b/src/tmuxp/types.py @@ -9,10 +9,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Union +import typing as t -if TYPE_CHECKING: +if t.TYPE_CHECKING: from os import PathLike -StrPath = Union[str, "PathLike[str]"] +StrPath = t.Union[str, "PathLike[str]"] """:class:`os.PathLike` or :class:`str`""" diff --git a/tests/cli/test_convert.py b/tests/cli/test_convert.py index 6946040948..f57f771f77 100644 --- a/tests/cli/test_convert.py +++ b/tests/cli/test_convert.py @@ -5,13 +5,13 @@ import contextlib import io import json -from typing import TYPE_CHECKING +import typing as t import pytest from tmuxp import cli -if TYPE_CHECKING: +if t.TYPE_CHECKING: import pathlib diff --git a/tests/cli/test_debug_info.py b/tests/cli/test_debug_info.py index 5275fdaefe..1729f1e9cc 100644 --- a/tests/cli/test_debug_info.py +++ b/tests/cli/test_debug_info.py @@ -2,11 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import typing as t from tmuxp import cli -if TYPE_CHECKING: +if t.TYPE_CHECKING: import pathlib import pytest diff --git a/tests/cli/test_import.py b/tests/cli/test_import.py index 8dc8c9ec96..a3bfe71a7f 100644 --- a/tests/cli/test_import.py +++ b/tests/cli/test_import.py @@ -4,14 +4,14 @@ import contextlib import io -from typing import TYPE_CHECKING +import typing as t import pytest from tests.fixtures import utils as test_utils from tmuxp import cli -if TYPE_CHECKING: +if t.TYPE_CHECKING: import pathlib diff --git a/tests/cli/test_ls.py b/tests/cli/test_ls.py index 6f3a0da907..1811e636c4 100644 --- a/tests/cli/test_ls.py +++ b/tests/cli/test_ls.py @@ -4,11 +4,11 @@ import contextlib import pathlib -from typing import TYPE_CHECKING +import typing as t from tmuxp import cli -if TYPE_CHECKING: +if t.TYPE_CHECKING: import pytest diff --git a/tests/test_util.py b/tests/test_util.py index 12a426f92c..5b58b493d5 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import typing as t import pytest @@ -12,7 +12,7 @@ from .constants import FIXTURE_PATH -if TYPE_CHECKING: +if t.TYPE_CHECKING: from libtmux.server import Server diff --git a/tests/tests/test_helpers.py b/tests/tests/test_helpers.py index 53088d4614..0888cd5e03 100644 --- a/tests/tests/test_helpers.py +++ b/tests/tests/test_helpers.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import typing as t import pytest from libtmux.test import get_test_session_name, temp_session -if TYPE_CHECKING: +if t.TYPE_CHECKING: from libtmux.server import Server From 5b04bf4d1754a92e00ce13e6d2db0488b4d72902 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Jan 2025 08:30:03 -0600 Subject: [PATCH 8/8] docs(CHANGES) Note PEP 563 compatibility --- CHANGES | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES b/CHANGES index a911f7aa02..73d61f9a9e 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,17 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force +### Development + +#### chore: Implement PEP 563 deferred annotation resolution (#957) + +- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary runtime computations during type checking. +- Enable Ruff checks for PEP-compliant annotations: + - [non-pep585-annotation (UP006)](https://docs.astral.sh/ruff/rules/non-pep585-annotation/) + - [non-pep604-annotation (UP007)](https://docs.astral.sh/ruff/rules/non-pep604-annotation/) + +For more details on PEP 563, see: https://peps.python.org/pep-0563/ + ## tmuxp 1.50.1 (2024-12-24) ### Development