66- **License**: MIT
77- **Description**: Snapshot data structure for tmux objects
88
9- Note on type checking:
10- The snapshot classes intentionally override properties from parent classes with
11- slightly different return types (covariant types - e.g., returning WindowSnapshot
12- instead of Window). This is type-safe at runtime but causes mypy warnings. We use
13- type: ignore[override] comments on these properties and add proper typing.
9+ This module provides hierarchical snapshots of tmux objects (Server, Session,
10+ Window, Pane) that are immutable and maintain the relationships between objects.
1411"""
1512
1613from __future__ import annotations
3229from libtmux .session import Session
3330from libtmux .window import Window
3431
35- if t . TYPE_CHECKING :
36- PaneT = t .TypeVar ("PaneT" , bound = Pane , covariant = True )
37- WindowT = t .TypeVar ("WindowT" , bound = Window , covariant = True )
38- SessionT = t .TypeVar ("SessionT" , bound = Session , covariant = True )
39- ServerT = t .TypeVar ("ServerT" , bound = Server , covariant = True )
32+ # Define type variables for generic typing
33+ PaneT = t .TypeVar ("PaneT" , bound = Pane , covariant = True )
34+ WindowT = t .TypeVar ("WindowT" , bound = Window , covariant = True )
35+ SessionT = t .TypeVar ("SessionT" , bound = Session , covariant = True )
36+ ServerT = t .TypeVar ("ServerT" , bound = Server , covariant = True )
4037
38+ # Forward references for type definitions
39+ ServerSnapshot_t = t .TypeVar ("ServerSnapshot_t" , bound = "ServerSnapshot" )
40+ SessionSnapshot_t = t .TypeVar ("SessionSnapshot_t" , bound = "SessionSnapshot" )
41+ WindowSnapshot_t = t .TypeVar ("WindowSnapshot_t" , bound = "WindowSnapshot" )
42+ PaneSnapshot_t = t .TypeVar ("PaneSnapshot_t" , bound = "PaneSnapshot" )
4143
42- # Make base classes implement Sealable
44+
45+ # Make base classes implement Sealable and use Generics
4346class _SealablePaneBase (Pane , Sealable ):
4447 """Base class for sealable pane classes."""
4548
4649
47- class _SealableWindowBase (Window , Sealable ):
48- """Base class for sealable window classes."""
50+ class _SealableWindowBase (Window , Sealable , t .Generic [PaneT ]):
51+ """Base class for sealable window classes with generic pane type."""
52+
53+ @property
54+ def panes (self ) -> QueryList [PaneT ]:
55+ """Return panes with the appropriate generic type."""
56+ return t .cast (QueryList [PaneT ], super ().panes )
57+
58+ @property
59+ def active_pane (self ) -> PaneT | None :
60+ """Return active pane with the appropriate generic type."""
61+ return t .cast (t .Optional [PaneT ], super ().active_pane )
62+
63+
64+ class _SealableSessionBase (Session , Sealable , t .Generic [WindowT , PaneT ]):
65+ """Base class for sealable session classes with generic window and pane types."""
66+
67+ @property
68+ def windows (self ) -> QueryList [WindowT ]:
69+ """Return windows with the appropriate generic type."""
70+ return t .cast (QueryList [WindowT ], super ().windows )
71+
72+ @property
73+ def active_window (self ) -> WindowT | None :
74+ """Return active window with the appropriate generic type."""
75+ return t .cast (t .Optional [WindowT ], super ().active_window )
76+
77+ @property
78+ def active_pane (self ) -> PaneT | None :
79+ """Return active pane with the appropriate generic type."""
80+ return t .cast (t .Optional [PaneT ], super ().active_pane )
4981
5082
51- class _SealableSessionBase (Session , Sealable ):
52- """Base class for sealable session classes."""
83+ class _SealableServerBase (Server , Sealable , t .Generic [SessionT , WindowT , PaneT ]):
84+ """Generic base for sealable server with typed session, window, and pane."""
85+
86+ @property
87+ def sessions (self ) -> QueryList [SessionT ]:
88+ """Return sessions with the appropriate generic type."""
89+ return t .cast (QueryList [SessionT ], super ().sessions )
5390
91+ @property
92+ def windows (self ) -> QueryList [WindowT ]:
93+ """Return windows with the appropriate generic type."""
94+ return t .cast (QueryList [WindowT ], super ().windows )
5495
55- class _SealableServerBase (Server , Sealable ):
56- """Base class for sealable server classes."""
96+ @property
97+ def panes (self ) -> QueryList [PaneT ]:
98+ """Return panes with the appropriate generic type."""
99+ return t .cast (QueryList [PaneT ], super ().panes )
57100
58101
59102@frozen_dataclass_sealable
@@ -251,7 +294,7 @@ def from_pane(
251294
252295
253296@frozen_dataclass_sealable
254- class WindowSnapshot (_SealableWindowBase ):
297+ class WindowSnapshot (_SealableWindowBase [ PaneSnapshot ] ):
255298 """A read-only snapshot of a tmux window.
256299
257300 This maintains compatibility with the original Window class but prevents
@@ -404,7 +447,7 @@ def from_window(
404447
405448
406449@frozen_dataclass_sealable
407- class SessionSnapshot (_SealableSessionBase ):
450+ class SessionSnapshot (_SealableSessionBase [ WindowSnapshot , PaneSnapshot ] ):
408451 """A read-only snapshot of a tmux session.
409452
410453 This maintains compatibility with the original Session class but prevents
@@ -551,7 +594,9 @@ def from_session(
551594
552595
553596@frozen_dataclass_sealable
554- class ServerSnapshot (_SealableServerBase ):
597+ class ServerSnapshot (
598+ _SealableServerBase [SessionSnapshot , WindowSnapshot , PaneSnapshot ]
599+ ):
555600 """A read-only snapshot of a server.
556601
557602 Examples
@@ -690,6 +735,10 @@ def from_server(
690735 return snapshot
691736
692737
738+ # Define a Union type for snapshot classes
739+ SnapshotType = t .Union [ServerSnapshot , SessionSnapshot , WindowSnapshot , PaneSnapshot ]
740+
741+
693742def _create_session_snapshot_safely (
694743 session : Session , include_content : bool , server_snapshot : ServerSnapshot
695744) -> SessionSnapshot | None :
@@ -741,12 +790,9 @@ def _create_session_snapshot_safely(
741790
742791
743792def filter_snapshot (
744- snapshot : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot ,
745- filter_func : t .Callable [
746- [ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot ],
747- bool ,
748- ],
749- ) -> ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot | None :
793+ snapshot : SnapshotType ,
794+ filter_func : t .Callable [[SnapshotType ], bool ],
795+ ) -> SnapshotType | None :
750796 """Filter a snapshot hierarchy based on a filter function.
751797
752798 This will prune the snapshot tree, removing any objects that don't match the filter.
@@ -755,24 +801,24 @@ def filter_snapshot(
755801
756802 Parameters
757803 ----------
758- snapshot : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot
804+ snapshot : SnapshotType
759805 The snapshot to filter
760806 filter_func : Callable
761807 A function that takes a snapshot object and returns True to keep it
762808 or False to filter it out
763809
764810 Returns
765811 -------
766- ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot | None
812+ SnapshotType | None
767813 A new filtered snapshot, or None if everything was filtered out
768814 """
769815 if isinstance (snapshot , ServerSnapshot ):
770- filtered_sessions = []
816+ filtered_sessions : list [ SessionSnapshot ] = []
771817
772818 for sess in snapshot .sessions_snapshot :
773819 session_copy = filter_snapshot (sess , filter_func )
774- if session_copy is not None :
775- filtered_sessions .append (t . cast ( SessionSnapshot , session_copy ) )
820+ if session_copy is not None and isinstance ( session_copy , SessionSnapshot ) :
821+ filtered_sessions .append (session_copy )
776822
777823 if not filter_func (snapshot ) and not filtered_sessions :
778824 return None
@@ -793,12 +839,12 @@ def filter_snapshot(
793839 return server_copy
794840
795841 if isinstance (snapshot , SessionSnapshot ):
796- filtered_windows = []
842+ filtered_windows : list [ WindowSnapshot ] = []
797843
798844 for w in snapshot .windows_snapshot :
799845 window_copy = filter_snapshot (w , filter_func )
800- if window_copy is not None :
801- filtered_windows .append (t . cast ( WindowSnapshot , window_copy ) )
846+ if window_copy is not None and isinstance ( window_copy , WindowSnapshot ) :
847+ filtered_windows .append (window_copy )
802848
803849 if not filter_func (snapshot ) and not filtered_windows :
804850 return None
@@ -808,8 +854,6 @@ def filter_snapshot(
808854 return session_copy
809855
810856 if isinstance (snapshot , WindowSnapshot ):
811- filtered_panes = []
812-
813857 filtered_panes = [p for p in snapshot .panes_snapshot if filter_func (p )]
814858
815859 if not filter_func (snapshot ) and not filtered_panes :
@@ -828,15 +872,15 @@ def filter_snapshot(
828872
829873
830874def snapshot_to_dict (
831- snapshot : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot | t .Any ,
875+ snapshot : SnapshotType | t .Any ,
832876) -> dict [str , t .Any ]:
833877 """Convert a snapshot to a dictionary, avoiding circular references.
834878
835879 This is useful for serializing snapshots to JSON or other formats.
836880
837881 Parameters
838882 ----------
839- snapshot : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot | Any
883+ snapshot : SnapshotType | Any
840884 The snapshot to convert to a dictionary
841885
842886 Returns
@@ -914,7 +958,7 @@ def snapshot_active_only(
914958 """
915959
916960 def is_active (
917- obj : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot ,
961+ obj : SnapshotType ,
918962 ) -> bool :
919963 """Return True if the object is active."""
920964 if isinstance (obj , PaneSnapshot ):
@@ -927,4 +971,4 @@ def is_active(
927971 if filtered is None :
928972 error_msg = "No active objects found!"
929973 raise ValueError (error_msg )
930- return t .cast (" ServerSnapshot" , filtered )
974+ return t .cast (ServerSnapshot , filtered )
0 commit comments