Skip to content

Commit eeed15b

Browse files
authored
Deprecate tmux versions below 3.2a (#606)
Add deprecation warning (`FutureWarning`) for tmux versions below 3.2a, introduce `TMUX_SOFT_MIN_VERSION` constant for deprecation threshold, and prepare for future hard minimum version bump. Motivation: tmux 3.2a was released in April 2021 and is the version shipped with Ubuntu 22.04 LTS. Older versions (3.1 and below) are only found on EOL platforms like Ubuntu 20.04. Deprecating these versions allows libtmux to: - Remove legacy compatibility code in future releases - Take advantage of newer tmux features (e.g., `format-defaults`, extended hooks, `display-menu` APIs) - Align supported versions with actively maintained distributions Changes: Source (`src/libtmux/common.py`): - `TMUX_SOFT_MIN_VERSION`: New constant set to `"3.2a"` - `_version_deprecation_checked`: Module-level flag ensuring warning fires only once per process - `_check_deprecated_version()`: New helper that emits `FutureWarning` when tmux version is below 3.2a - `get_version()`: Now calls `_check_deprecated_version()` to trigger warning on first version check Environment Variable: - Users can suppress warning via `LIBTMUX_SUPPRESS_VERSION_WARNING=1` Tests (`tests/test_common.py`): - `test_version_deprecation_warning` - 5 parametrized cases covering deprecated versions, current versions, and env var suppression - `test_version_deprecation_warns_once` - Verifies warning fires once - `test_version_deprecation_via_get_version` - Integration test verifying warning through full `get_version()` call chain Documentation: - `docs/quickstart.md`: Updated Requirements with version recommendations - `CHANGES`: Added Deprecations and Internal sections for 0.48.x Future Work: In a future release (e.g., 0.49.0 or 0.50.0): - Bump `TMUX_MIN_VERSION` from `"1.8"` to `"3.2a"` - Remove `_check_deprecated_version()` helper and `TMUX_SOFT_MIN_VERSION` - Remove tests that mock old version values See Also: #516, #594, #603, #604
2 parents 204785b + 4ed7b79 commit eeed15b

File tree

4 files changed

+180
-3
lines changed

4 files changed

+180
-3
lines changed

CHANGES

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,17 @@ $ uvx --from 'libtmux' --prerelease allow python
3232

3333
<!-- To maintainers and contributors: Please add notes for the forthcoming version below -->
3434

35-
- _Future release notes will be placed here_
35+
_Future release notes will be placed here_
36+
37+
### Deprecations
38+
39+
- tmux versions below 3.2a are now deprecated (#606). A `FutureWarning` will
40+
be emitted on first use. Support for these versions will be removed in a future
41+
release. Set `LIBTMUX_SUPPRESS_VERSION_WARNING=1` to suppress the warning.
42+
43+
### Internal
44+
45+
- Added `TMUX_SOFT_MIN_VERSION` constant (3.2a) for deprecation threshold (#606)
3646

3747
### What's new
3848

docs/quickstart.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ from inside a live tmux session.
1212

1313
## Requirements
1414

15-
- [tmux]
15+
- [tmux] 3.2a or newer (recommended)
16+
- tmux 1.8 - 3.1 are deprecated and will be unsupported in a future release
1617
- [pip] - for this handbook's examples
1718

1819
[tmux]: https://tmux.github.io/

src/libtmux/common.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,44 @@
2929
#: Most recent version of tmux supported
3030
TMUX_MAX_VERSION = "3.6"
3131

32+
#: Minimum version before deprecation warning is shown
33+
TMUX_SOFT_MIN_VERSION = "3.2a"
34+
3235
SessionDict = dict[str, t.Any]
3336
WindowDict = dict[str, t.Any]
3437
WindowOptionDict = dict[str, t.Any]
3538
PaneDict = dict[str, t.Any]
3639

40+
#: Flag to ensure deprecation warning is only shown once per process
41+
_version_deprecation_checked: bool = False
42+
43+
44+
def _check_deprecated_version(version: LooseVersion) -> None:
45+
"""Check if tmux version is deprecated and warn once.
46+
47+
This is called from get_version() on first invocation.
48+
"""
49+
global _version_deprecation_checked
50+
if _version_deprecation_checked:
51+
return
52+
_version_deprecation_checked = True
53+
54+
import os
55+
import warnings
56+
57+
if os.environ.get("LIBTMUX_SUPPRESS_VERSION_WARNING"):
58+
return
59+
60+
if version < LooseVersion(TMUX_SOFT_MIN_VERSION):
61+
warnings.warn(
62+
f"tmux {version} is deprecated and will be unsupported in a future "
63+
f"libtmux release. Please upgrade to tmux {TMUX_SOFT_MIN_VERSION} "
64+
"or newer. Set LIBTMUX_SUPPRESS_VERSION_WARNING=1 to suppress this "
65+
"warning.",
66+
FutureWarning,
67+
stacklevel=4,
68+
)
69+
3770

3871
class EnvironmentMixin:
3972
"""Mixin for manager session and server level environment variables in tmux."""
@@ -303,7 +336,9 @@ def get_version() -> LooseVersion:
303336

304337
version = re.sub(r"[a-z-]", "", version)
305338

306-
return LooseVersion(version)
339+
version_obj = LooseVersion(version)
340+
_check_deprecated_version(version_obj)
341+
return version_obj
307342

308343

309344
def has_version(version: str) -> bool:

tests/test_common.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,3 +508,134 @@ def mock_get_version() -> LooseVersion:
508508
elif check_type == "type_check":
509509
assert mock_version is not None # For type checker
510510
assert isinstance(has_version(mock_version), bool)
511+
512+
513+
class VersionDeprecationFixture(t.NamedTuple):
514+
"""Test fixture for version deprecation warning."""
515+
516+
test_id: str
517+
version: str
518+
suppress_env: bool
519+
expected_warning: bool
520+
521+
522+
VERSION_DEPRECATION_FIXTURES: list[VersionDeprecationFixture] = [
523+
VersionDeprecationFixture(
524+
test_id="deprecated_version_warns",
525+
version="3.1",
526+
suppress_env=False,
527+
expected_warning=True,
528+
),
529+
VersionDeprecationFixture(
530+
test_id="old_deprecated_version_warns",
531+
version="2.9",
532+
suppress_env=False,
533+
expected_warning=True,
534+
),
535+
VersionDeprecationFixture(
536+
test_id="current_version_no_warning",
537+
version="3.2a",
538+
suppress_env=False,
539+
expected_warning=False,
540+
),
541+
VersionDeprecationFixture(
542+
test_id="newer_version_no_warning",
543+
version="3.5",
544+
suppress_env=False,
545+
expected_warning=False,
546+
),
547+
VersionDeprecationFixture(
548+
test_id="env_var_suppresses_warning",
549+
version="3.0",
550+
suppress_env=True,
551+
expected_warning=False,
552+
),
553+
]
554+
555+
556+
@pytest.mark.parametrize(
557+
list(VersionDeprecationFixture._fields),
558+
VERSION_DEPRECATION_FIXTURES,
559+
ids=[test.test_id for test in VERSION_DEPRECATION_FIXTURES],
560+
)
561+
def test_version_deprecation_warning(
562+
test_id: str,
563+
version: str,
564+
suppress_env: bool,
565+
expected_warning: bool,
566+
monkeypatch: pytest.MonkeyPatch,
567+
) -> None:
568+
"""Test version deprecation warning behavior."""
569+
import warnings
570+
571+
import libtmux.common
572+
573+
# Reset the warning flag for each test
574+
monkeypatch.setattr(libtmux.common, "_version_deprecation_checked", False)
575+
576+
# Set or clear the suppress env var
577+
if suppress_env:
578+
monkeypatch.setenv("LIBTMUX_SUPPRESS_VERSION_WARNING", "1")
579+
else:
580+
monkeypatch.delenv("LIBTMUX_SUPPRESS_VERSION_WARNING", raising=False)
581+
582+
with warnings.catch_warnings(record=True) as w:
583+
warnings.simplefilter("always")
584+
libtmux.common._check_deprecated_version(LooseVersion(version))
585+
586+
if expected_warning:
587+
assert len(w) == 1
588+
assert issubclass(w[0].category, FutureWarning)
589+
assert version in str(w[0].message)
590+
assert "3.2a" in str(w[0].message)
591+
else:
592+
assert len(w) == 0
593+
594+
595+
def test_version_deprecation_warns_once(monkeypatch: pytest.MonkeyPatch) -> None:
596+
"""Test that deprecation warning only fires once per process."""
597+
import warnings
598+
599+
import libtmux.common
600+
601+
monkeypatch.setattr(libtmux.common, "_version_deprecation_checked", False)
602+
monkeypatch.delenv("LIBTMUX_SUPPRESS_VERSION_WARNING", raising=False)
603+
604+
with warnings.catch_warnings(record=True) as w:
605+
warnings.simplefilter("always")
606+
libtmux.common._check_deprecated_version(LooseVersion("3.1"))
607+
libtmux.common._check_deprecated_version(LooseVersion("3.1"))
608+
609+
assert len(w) == 1
610+
611+
612+
def test_version_deprecation_via_get_version(monkeypatch: pytest.MonkeyPatch) -> None:
613+
"""Test deprecation warning fires through get_version() call.
614+
615+
This integration test verifies the warning is emitted when calling
616+
get_version() with an old tmux version, testing the full call chain.
617+
"""
618+
import warnings
619+
620+
import libtmux.common
621+
622+
class MockTmuxOutput:
623+
stdout: t.ClassVar = ["tmux 3.1"]
624+
stderr: t.ClassVar[list[str]] = []
625+
626+
def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> MockTmuxOutput:
627+
return MockTmuxOutput()
628+
629+
monkeypatch.setattr(libtmux.common, "_version_deprecation_checked", False)
630+
monkeypatch.setattr(libtmux.common, "tmux_cmd", mock_tmux_cmd)
631+
monkeypatch.delenv("LIBTMUX_SUPPRESS_VERSION_WARNING", raising=False)
632+
633+
with warnings.catch_warnings(record=True) as w:
634+
warnings.simplefilter("always")
635+
version = libtmux.common.get_version()
636+
637+
assert str(version) == "3.1"
638+
assert len(w) == 1
639+
assert issubclass(w[0].category, FutureWarning)
640+
assert "3.1" in str(w[0].message)
641+
assert "3.2a" in str(w[0].message)

0 commit comments

Comments
 (0)