From f408f0c42cb499683a823e914247556535f1f381 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 12:11:25 -0600 Subject: [PATCH 1/8] common(feat): Add deprecation warning for tmux < 3.2a --- src/libtmux/common.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/libtmux/common.py b/src/libtmux/common.py index 886b160fb..2aa19f473 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -29,11 +29,44 @@ #: Most recent version of tmux supported TMUX_MAX_VERSION = "3.6" +#: Minimum version before deprecation warning is shown +TMUX_SOFT_MIN_VERSION = "3.2a" + SessionDict = dict[str, t.Any] WindowDict = dict[str, t.Any] WindowOptionDict = dict[str, t.Any] PaneDict = dict[str, t.Any] +#: Flag to ensure deprecation warning is only shown once per process +_version_deprecation_checked: bool = False + + +def _check_deprecated_version(version: LooseVersion) -> None: + """Check if tmux version is deprecated and warn once. + + This is called from get_version() on first invocation. + """ + global _version_deprecation_checked + if _version_deprecation_checked: + return + _version_deprecation_checked = True + + import os + import warnings + + if os.environ.get("LIBTMUX_SUPPRESS_VERSION_WARNING"): + return + + if version < LooseVersion(TMUX_SOFT_MIN_VERSION): + warnings.warn( + f"tmux {version} is deprecated and will be unsupported in a future " + f"libtmux release. Please upgrade to tmux {TMUX_SOFT_MIN_VERSION} " + "or newer. Set LIBTMUX_SUPPRESS_VERSION_WARNING=1 to suppress this " + "warning.", + DeprecationWarning, + stacklevel=4, + ) + class EnvironmentMixin: """Mixin for manager session and server level environment variables in tmux.""" @@ -303,7 +336,9 @@ def get_version() -> LooseVersion: version = re.sub(r"[a-z-]", "", version) - return LooseVersion(version) + version_obj = LooseVersion(version) + _check_deprecated_version(version_obj) + return version_obj def has_version(version: str) -> bool: From 8b893e3718ff318152c4753d20b672d370edcd2f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 12:19:06 -0600 Subject: [PATCH 2/8] test(common): Add tests for tmux version deprecation warning --- tests/test_common.py | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/test_common.py b/tests/test_common.py index 3aa045bc4..7c896fe4c 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -508,3 +508,102 @@ def mock_get_version() -> LooseVersion: elif check_type == "type_check": assert mock_version is not None # For type checker assert isinstance(has_version(mock_version), bool) + + +class VersionDeprecationFixture(t.NamedTuple): + """Test fixture for version deprecation warning.""" + + test_id: str + version: str + suppress_env: bool + expected_warning: bool + + +VERSION_DEPRECATION_FIXTURES: list[VersionDeprecationFixture] = [ + VersionDeprecationFixture( + test_id="deprecated_version_warns", + version="3.1", + suppress_env=False, + expected_warning=True, + ), + VersionDeprecationFixture( + test_id="old_deprecated_version_warns", + version="2.9", + suppress_env=False, + expected_warning=True, + ), + VersionDeprecationFixture( + test_id="current_version_no_warning", + version="3.2a", + suppress_env=False, + expected_warning=False, + ), + VersionDeprecationFixture( + test_id="newer_version_no_warning", + version="3.5", + suppress_env=False, + expected_warning=False, + ), + VersionDeprecationFixture( + test_id="env_var_suppresses_warning", + version="3.0", + suppress_env=True, + expected_warning=False, + ), +] + + +@pytest.mark.parametrize( + list(VersionDeprecationFixture._fields), + VERSION_DEPRECATION_FIXTURES, + ids=[test.test_id for test in VERSION_DEPRECATION_FIXTURES], +) +def test_version_deprecation_warning( + test_id: str, + version: str, + suppress_env: bool, + expected_warning: bool, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test version deprecation warning behavior.""" + import warnings + + import libtmux.common + + # Reset the warning flag for each test + monkeypatch.setattr(libtmux.common, "_version_deprecation_checked", False) + + # Set or clear the suppress env var + if suppress_env: + monkeypatch.setenv("LIBTMUX_SUPPRESS_VERSION_WARNING", "1") + else: + monkeypatch.delenv("LIBTMUX_SUPPRESS_VERSION_WARNING", raising=False) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + libtmux.common._check_deprecated_version(LooseVersion(version)) + + if expected_warning: + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert version in str(w[0].message) + assert "3.2a" in str(w[0].message) + else: + assert len(w) == 0 + + +def test_version_deprecation_warns_once(monkeypatch: pytest.MonkeyPatch) -> None: + """Test that deprecation warning only fires once per process.""" + import warnings + + import libtmux.common + + monkeypatch.setattr(libtmux.common, "_version_deprecation_checked", False) + monkeypatch.delenv("LIBTMUX_SUPPRESS_VERSION_WARNING", raising=False) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + libtmux.common._check_deprecated_version(LooseVersion("3.1")) + libtmux.common._check_deprecated_version(LooseVersion("3.1")) + + assert len(w) == 1 From f7fff0be72978d7766925b9502c8c7028c603dfe Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 12:19:11 -0600 Subject: [PATCH 3/8] docs(quickstart): Note tmux version requirements and deprecation --- docs/quickstart.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 90edfcbfc..9e74f6274 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -12,7 +12,8 @@ from inside a live tmux session. ## Requirements -- [tmux] +- [tmux] 3.2a or newer (recommended) + - tmux 1.8 - 3.1 are deprecated and will be unsupported in a future release - [pip] - for this handbook's examples [tmux]: https://tmux.github.io/ From ae9fa7ed3d0fbf58a4f8640509418e8c115967f5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 12:19:14 -0600 Subject: [PATCH 4/8] docs(CHANGES) Note tmux version deprecation warning --- CHANGES | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index dd4b1c328..b05506163 100644 --- a/CHANGES +++ b/CHANGES @@ -32,7 +32,17 @@ $ uvx --from 'libtmux' --prerelease allow python -- _Future release notes will be placed here_ +_Future release notes will be placed here_ + +### Deprecations + +- tmux versions below 3.2a are now deprecated (#606). A `DeprecationWarning` will + be emitted on first use. Support for these versions will be removed in a future + release. Set `LIBTMUX_SUPPRESS_VERSION_WARNING=1` to suppress the warning. + +### Internal + +- Added `TMUX_SOFT_MIN_VERSION` constant (3.2a) for deprecation threshold (#606) ### What's new From 4876e56728b4ace0fb9755b0bec808c006cb5275 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 12:57:24 -0600 Subject: [PATCH 5/8] common(fix): Use FutureWarning for tmux version deprecation --- src/libtmux/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtmux/common.py b/src/libtmux/common.py index 2aa19f473..6923f0e0f 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -63,7 +63,7 @@ def _check_deprecated_version(version: LooseVersion) -> None: f"libtmux release. Please upgrade to tmux {TMUX_SOFT_MIN_VERSION} " "or newer. Set LIBTMUX_SUPPRESS_VERSION_WARNING=1 to suppress this " "warning.", - DeprecationWarning, + FutureWarning, stacklevel=4, ) From 2ec6c737bd9c06d411c47f1e0d2f274e1a0a7d5a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 12:57:58 -0600 Subject: [PATCH 6/8] test(common): Update deprecation tests for FutureWarning --- tests/test_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_common.py b/tests/test_common.py index 7c896fe4c..f6cc8325b 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -585,7 +585,7 @@ def test_version_deprecation_warning( if expected_warning: assert len(w) == 1 - assert issubclass(w[0].category, DeprecationWarning) + assert issubclass(w[0].category, FutureWarning) assert version in str(w[0].message) assert "3.2a" in str(w[0].message) else: From 026f08dbefe08c9ff8d55ee522241f23cc89a554 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 12:58:20 -0600 Subject: [PATCH 7/8] docs(CHANGES) Note FutureWarning instead of DeprecationWarning --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b05506163..2d44bb2b3 100644 --- a/CHANGES +++ b/CHANGES @@ -36,7 +36,7 @@ _Future release notes will be placed here_ ### Deprecations -- tmux versions below 3.2a are now deprecated (#606). A `DeprecationWarning` will +- tmux versions below 3.2a are now deprecated (#606). A `FutureWarning` will be emitted on first use. Support for these versions will be removed in a future release. Set `LIBTMUX_SUPPRESS_VERSION_WARNING=1` to suppress the warning. From 4ed7b79eac4f7ce88c299fac7456bb862de789eb Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 28 Nov 2025 13:00:35 -0600 Subject: [PATCH 8/8] test(common): Add integration test for deprecation via get_version() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: Existing tests call _check_deprecated_version() directly, missing integration issues in the full call chain. what: - Add test_version_deprecation_via_get_version() that monkeypatches tmux_cmd and calls get_version() to verify warning plumbing - Tests the complete flow: tmux_cmd → version parsing → deprecation check --- tests/test_common.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_common.py b/tests/test_common.py index f6cc8325b..57a9e4745 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -607,3 +607,35 @@ def test_version_deprecation_warns_once(monkeypatch: pytest.MonkeyPatch) -> None libtmux.common._check_deprecated_version(LooseVersion("3.1")) assert len(w) == 1 + + +def test_version_deprecation_via_get_version(monkeypatch: pytest.MonkeyPatch) -> None: + """Test deprecation warning fires through get_version() call. + + This integration test verifies the warning is emitted when calling + get_version() with an old tmux version, testing the full call chain. + """ + import warnings + + import libtmux.common + + class MockTmuxOutput: + stdout: t.ClassVar = ["tmux 3.1"] + stderr: t.ClassVar[list[str]] = [] + + def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> MockTmuxOutput: + return MockTmuxOutput() + + monkeypatch.setattr(libtmux.common, "_version_deprecation_checked", False) + monkeypatch.setattr(libtmux.common, "tmux_cmd", mock_tmux_cmd) + monkeypatch.delenv("LIBTMUX_SUPPRESS_VERSION_WARNING", raising=False) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + version = libtmux.common.get_version() + + assert str(version) == "3.1" + assert len(w) == 1 + assert issubclass(w[0].category, FutureWarning) + assert "3.1" in str(w[0].message) + assert "3.2a" in str(w[0].message)