Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
dd67944
Hooks(refactor[typing]): Narrow hook dict values
tony Jan 3, 2026
01270d9
Temporary(refactor[typing]): Tighten contextmanager generator types
tony Jan 3, 2026
3742c9a
Tests(refactor[typing]): Tighten version compare fixtures
tony Jan 3, 2026
91d027f
Tests(refactor[typing]): Use options dict alias for sparse array cases
tony Jan 3, 2026
d26d3cb
Tests(refactor[typing]): Tighten deprecated window method args
tony Jan 3, 2026
9fdbfa7
Tests(refactor[typing]): Narrow dataclasses output alias
tony Jan 3, 2026
29b9b1f
Tests(refactor[typing]): Narrow option fixture expected type
tony Jan 3, 2026
30ab125
Tests(refactor[typing]): Tighten hook fixture kwarg shapes
tony Jan 3, 2026
7f766f2
Tests(refactor[typing]): Narrow option test case annotations
tony Jan 3, 2026
b69d960
Tests(refactor[typing]): Tighten tmux_cmd test mocks
tony Jan 3, 2026
103c042
PytestPlugin(refactor[typing]): Type session params fixture
tony Jan 3, 2026
844f793
TestHelpers(refactor[typing]): Type temp session/window helpers
tony Jan 3, 2026
681265c
Tests(refactor[typing]): Tighten QueryList filter param types
tony Jan 3, 2026
9e77391
TestHelpers(refactor[typing]): Tighten temp helper kwargs
tony Jan 3, 2026
2d91429
Exc(refactor[typing]): Narrow VariableUnpackingError variable type
tony Jan 3, 2026
5ce7637
Session(refactor[typing]): Narrow deprecated lookup signatures
tony Jan 3, 2026
e70c3e9
Window(refactor[typing]): Narrow deprecated lookup signatures
tony Jan 3, 2026
654c60b
Pane(refactor[typing]): Narrow deprecated lookup signatures
tony Jan 3, 2026
24ee357
Server(refactor[typing]): Narrow deprecated filter signatures
tony Jan 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/libtmux/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class WaitTimeout(LibTmuxException):
class VariableUnpackingError(LibTmuxException):
"""Error unpacking variable."""

def __init__(self, variable: t.Any | None = None, *args: object) -> None:
def __init__(self, variable: object | None = None, *args: object) -> None:
return super().__init__(f"Unexpected variable: {variable!s}")


Expand Down
2 changes: 1 addition & 1 deletion src/libtmux/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
if t.TYPE_CHECKING:
from typing_extensions import Self

HookDict = dict[str, t.Any]
HookDict = dict[str, int | str]
HookValues = dict[int, str] | SparseArray[str] | list[str]

logger = logging.getLogger(__name__)
Expand Down
4 changes: 2 additions & 2 deletions src/libtmux/pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ def split_window(
version="0.33.0",
)

def get(self, key: str, default: t.Any | None = None) -> t.Any:
def get(self, key: str, default: object | None = None) -> object:
"""Return key-based lookup. Deprecated by attributes.

.. deprecated:: 0.17
Expand All @@ -993,7 +993,7 @@ def get(self, key: str, default: t.Any | None = None) -> t.Any:
version="0.17.0",
)

def __getitem__(self, key: str) -> t.Any:
def __getitem__(self, key: str) -> object:
"""Return item lookup by key. Deprecated in favor of attributes.

.. deprecated:: 0.17
Expand Down
17 changes: 15 additions & 2 deletions src/libtmux/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@
USING_ZSH = "zsh" in os.getenv("SHELL", "")


class SessionParams(t.TypedDict, total=False):
"""Keyword arguments for :meth:`libtmux.Server.new_session` in tests."""

kill_session: bool
attach: bool
start_directory: os.PathLike[str] | str | None
window_name: str | None
window_command: str | None
x: int | t.Literal["-"] | None
y: int | t.Literal["-"] | None
environment: dict[str, str] | None


@pytest.fixture(scope="session")
def home_path(tmp_path_factory: pytest.TempPathFactory) -> pathlib.Path:
"""Temporary `/home/` path."""
Expand Down Expand Up @@ -151,7 +164,7 @@ def fin() -> None:


@pytest.fixture
def session_params() -> dict[str, t.Any]:
def session_params() -> SessionParams:
"""Return new, temporary :class:`libtmux.Session`.

>>> import pytest
Expand Down Expand Up @@ -191,7 +204,7 @@ def session_params() -> dict[str, t.Any]:
@pytest.fixture
def session(
request: pytest.FixtureRequest,
session_params: dict[str, t.Any],
session_params: SessionParams,
server: Server,
) -> Session:
"""Return new, temporary :class:`libtmux.Session`.
Expand Down
4 changes: 2 additions & 2 deletions src/libtmux/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ def get_by_id(self, session_id: str) -> Session | None:
version="0.16.0",
)

def where(self, kwargs: dict[str, t.Any]) -> list[Session]:
def where(self, kwargs: dict[str, object]) -> list[Session]:
"""Filter through sessions, return list of :class:`Session`.

.. deprecated:: 0.17
Expand All @@ -763,7 +763,7 @@ def where(self, kwargs: dict[str, t.Any]) -> list[Session]:
version="0.17.0",
)

def find_where(self, kwargs: dict[str, t.Any]) -> Session | None:
def find_where(self, kwargs: dict[str, object]) -> Session | None:
"""Filter through sessions, return first :class:`Session`.

.. deprecated:: 0.17
Expand Down
8 changes: 4 additions & 4 deletions src/libtmux/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ def kill_session(self) -> None:
version="0.30.0",
)

def get(self, key: str, default: t.Any | None = None) -> t.Any:
def get(self, key: str, default: object | None = None) -> object:
"""Return key-based lookup. Deprecated by attributes.

.. deprecated:: 0.17
Expand All @@ -713,7 +713,7 @@ def get(self, key: str, default: t.Any | None = None) -> t.Any:
version="0.17.0",
)

def __getitem__(self, key: str) -> t.Any:
def __getitem__(self, key: str) -> object:
"""Return item lookup by key. Deprecated in favor of attributes.

.. deprecated:: 0.17
Expand Down Expand Up @@ -742,7 +742,7 @@ def get_by_id(self, session_id: str) -> Window | None:
version="0.16.0",
)

def where(self, kwargs: dict[str, t.Any]) -> list[Window]:
def where(self, kwargs: dict[str, object]) -> list[Window]:
"""Filter through windows, return list of :class:`Window`.

.. deprecated:: 0.17
Expand All @@ -756,7 +756,7 @@ def where(self, kwargs: dict[str, t.Any]) -> list[Window]:
version="0.17.0",
)

def find_where(self, kwargs: dict[str, t.Any]) -> Window | None:
def find_where(self, kwargs: dict[str, object]) -> Window | None:
"""Filter through windows, return first :class:`Window`.

.. deprecated:: 0.17
Expand Down
181 changes: 149 additions & 32 deletions src/libtmux/test/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@

if t.TYPE_CHECKING:
import sys
from collections.abc import Generator

from typing_extensions import Unpack

from libtmux._internal.types import StrPath
from libtmux.constants import WindowDirection
from libtmux.server import Server
from libtmux.session import Session
from libtmux.window import Window
Expand All @@ -22,12 +25,107 @@
pass


class TempSessionParams(t.TypedDict, total=False):
"""Keyword arguments for :func:`temp_session`."""

session_name: str | None
kill_session: bool
attach: bool
start_directory: StrPath | None
window_name: str | None
window_command: str | None
x: int | t.Literal["-"] | None
y: int | t.Literal["-"] | None
environment: dict[str, str] | None


class TempSessionKwargs(t.TypedDict, total=False):
"""Keyword arguments forwarded to :meth:`Server.new_session`."""

kill_session: bool
attach: bool
start_directory: StrPath | None
window_name: str | None
window_command: str | None
x: int | t.Literal["-"] | None
y: int | t.Literal["-"] | None
environment: dict[str, str] | None


class TempWindowParams(t.TypedDict, total=False):
"""Keyword arguments for :func:`temp_window`."""

window_name: str | None
start_directory: StrPath | None
attach: bool
window_index: str
window_shell: str | None
environment: dict[str, str] | None
direction: WindowDirection | None
target_window: str | None


class TempWindowKwargs(t.TypedDict, total=False):
"""Keyword arguments forwarded to :meth:`Session.new_window`."""

start_directory: StrPath | None
attach: bool
window_index: str
window_shell: str | None
environment: dict[str, str] | None
direction: WindowDirection | None
target_window: str | None


@contextlib.contextmanager
def _temp_session(
server: Server,
*args: t.Any,
**kwargs: object,
) -> t.Iterator[Session]:
kwargs_typed = t.cast("TempSessionParams", dict(kwargs))
if "session_name" in kwargs_typed:
session_name = kwargs_typed["session_name"]
else:
session_name = get_test_session_name(server)
kwargs_no_name = t.cast(
"TempSessionKwargs",
{k: v for k, v in kwargs_typed.items() if k != "session_name"},
)

session = server.new_session(
session_name,
*args,
**kwargs_no_name,
)

try:
yield session
finally:
if isinstance(session_name, str) and server.has_session(session_name):
session.kill()


@t.overload
def temp_session(
server: Server,
**kwargs: Unpack[TempSessionParams],
) -> contextlib.AbstractContextManager[Session]: ...


@t.overload
def temp_session(
server: Server,
*args: t.Any,
**kwargs: t.Any,
) -> Generator[Session, t.Any, t.Any]:
**kwargs: object,
) -> contextlib.AbstractContextManager[Session]: ...


def temp_session(
server: Server,
*args: t.Any,
**kwargs: object,
) -> contextlib.AbstractContextManager[Session]:
"""
Return a context manager with a temporary session.

Expand Down Expand Up @@ -58,27 +156,63 @@ def temp_session(
... session.new_window(window_name='my window')
Window(@3 2:my window, Session($... ...))
"""
if "session_name" in kwargs:
session_name = kwargs.pop("session_name")
return _temp_session(server, *args, **kwargs)


@contextlib.contextmanager
def _temp_window(
session: Session,
*args: t.Any,
**kwargs: object,
) -> t.Iterator[Window]:
kwargs_typed = t.cast("TempWindowParams", dict(kwargs))
if "window_name" in kwargs_typed:
window_name = kwargs_typed["window_name"]
else:
session_name = get_test_session_name(server)
window_name = get_test_window_name(session)
kwargs_no_name = t.cast(
"TempWindowKwargs",
{k: v for k, v in kwargs_typed.items() if k != "window_name"},
)

session = server.new_session(session_name, *args, **kwargs)
window = session.new_window(
window_name,
*args,
**kwargs_no_name,
)

# Get ``window_id`` before returning it, it may be killed within context.
window_id = window.window_id
assert window_id is not None
assert isinstance(window_id, str)

try:
yield session
yield window
finally:
if server.has_session(session_name):
session.kill()
return
if len(session.windows.filter(window_id=window_id)) > 0:
window.kill()


@contextlib.contextmanager
@t.overload
def temp_window(
session: Session,
**kwargs: Unpack[TempWindowParams],
) -> contextlib.AbstractContextManager[Window]: ...


@t.overload
def temp_window(
session: Session,
*args: t.Any,
**kwargs: t.Any,
) -> Generator[Window, t.Any, t.Any]:
**kwargs: object,
) -> contextlib.AbstractContextManager[Window]: ...


def temp_window(
session: Session,
*args: t.Any,
**kwargs: object,
) -> contextlib.AbstractContextManager[Window]:
"""
Return a context manager with a temporary window.

Expand Down Expand Up @@ -115,21 +249,4 @@ def temp_window(
... window.split()
Pane(%4 Window(@3 2:libtmux_..., Session($1 libtmux_...)))
"""
if "window_name" not in kwargs:
window_name = get_test_window_name(session)
else:
window_name = kwargs.pop("window_name")

window = session.new_window(window_name, *args, **kwargs)

# Get ``window_id`` before returning it, it may be killed within context.
window_id = window.window_id
assert window_id is not None
assert isinstance(window_id, str)

try:
yield window
finally:
if len(session.windows.filter(window_id=window_id)) > 0:
window.kill()
return
return _temp_window(session, *args, **kwargs)
8 changes: 4 additions & 4 deletions src/libtmux/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ def show_window_option(
global_=g,
)

def get(self, key: str, default: t.Any | None = None) -> t.Any:
def get(self, key: str, default: object | None = None) -> object:
"""Return key-based lookup. Deprecated by attributes.

.. deprecated:: 0.17
Expand All @@ -925,7 +925,7 @@ def get(self, key: str, default: t.Any | None = None) -> t.Any:
version="0.17.0",
)

def __getitem__(self, key: str) -> t.Any:
def __getitem__(self, key: str) -> object:
"""Return item lookup by key. Deprecated in favor of attributes.

.. deprecated:: 0.17
Expand Down Expand Up @@ -954,7 +954,7 @@ def get_by_id(self, pane_id: str) -> Pane | None:
version="0.16.0",
)

def where(self, kwargs: dict[str, t.Any]) -> list[Pane]:
def where(self, kwargs: dict[str, object]) -> list[Pane]:
"""Filter through panes, return list of :class:`Pane`.

.. deprecated:: 0.17
Expand All @@ -968,7 +968,7 @@ def where(self, kwargs: dict[str, t.Any]) -> list[Pane]:
version="0.17.0",
)

def find_where(self, kwargs: dict[str, t.Any]) -> Pane | None:
def find_where(self, kwargs: dict[str, object]) -> Pane | None:
"""Filter through panes, return first :class:`Pane`.

.. deprecated:: 0.17
Expand Down
Loading