6
6
- **License**: MIT
7
7
- **Description**: Snapshot data structure for tmux objects
8
8
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.
14
11
"""
15
12
16
13
from __future__ import annotations
32
29
from libtmux .session import Session
33
30
from libtmux .window import Window
34
31
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 )
40
37
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" )
41
43
42
- # Make base classes implement Sealable
44
+
45
+ # Make base classes implement Sealable and use Generics
43
46
class _SealablePaneBase (Pane , Sealable ):
44
47
"""Base class for sealable pane classes."""
45
48
46
49
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 )
49
81
50
82
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 )
53
90
91
+ @property
92
+ def windows (self ) -> QueryList [WindowT ]:
93
+ """Return windows with the appropriate generic type."""
94
+ return t .cast (QueryList [WindowT ], super ().windows )
54
95
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 )
57
100
58
101
59
102
@frozen_dataclass_sealable
@@ -251,7 +294,7 @@ def from_pane(
251
294
252
295
253
296
@frozen_dataclass_sealable
254
- class WindowSnapshot (_SealableWindowBase ):
297
+ class WindowSnapshot (_SealableWindowBase [ PaneSnapshot ] ):
255
298
"""A read-only snapshot of a tmux window.
256
299
257
300
This maintains compatibility with the original Window class but prevents
@@ -404,7 +447,7 @@ def from_window(
404
447
405
448
406
449
@frozen_dataclass_sealable
407
- class SessionSnapshot (_SealableSessionBase ):
450
+ class SessionSnapshot (_SealableSessionBase [ WindowSnapshot , PaneSnapshot ] ):
408
451
"""A read-only snapshot of a tmux session.
409
452
410
453
This maintains compatibility with the original Session class but prevents
@@ -551,7 +594,9 @@ def from_session(
551
594
552
595
553
596
@frozen_dataclass_sealable
554
- class ServerSnapshot (_SealableServerBase ):
597
+ class ServerSnapshot (
598
+ _SealableServerBase [SessionSnapshot , WindowSnapshot , PaneSnapshot ]
599
+ ):
555
600
"""A read-only snapshot of a server.
556
601
557
602
Examples
@@ -690,6 +735,10 @@ def from_server(
690
735
return snapshot
691
736
692
737
738
+ # Define a Union type for snapshot classes
739
+ SnapshotType = t .Union [ServerSnapshot , SessionSnapshot , WindowSnapshot , PaneSnapshot ]
740
+
741
+
693
742
def _create_session_snapshot_safely (
694
743
session : Session , include_content : bool , server_snapshot : ServerSnapshot
695
744
) -> SessionSnapshot | None :
@@ -741,12 +790,9 @@ def _create_session_snapshot_safely(
741
790
742
791
743
792
def 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 :
750
796
"""Filter a snapshot hierarchy based on a filter function.
751
797
752
798
This will prune the snapshot tree, removing any objects that don't match the filter.
@@ -755,24 +801,24 @@ def filter_snapshot(
755
801
756
802
Parameters
757
803
----------
758
- snapshot : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot
804
+ snapshot : SnapshotType
759
805
The snapshot to filter
760
806
filter_func : Callable
761
807
A function that takes a snapshot object and returns True to keep it
762
808
or False to filter it out
763
809
764
810
Returns
765
811
-------
766
- ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot | None
812
+ SnapshotType | None
767
813
A new filtered snapshot, or None if everything was filtered out
768
814
"""
769
815
if isinstance (snapshot , ServerSnapshot ):
770
- filtered_sessions = []
816
+ filtered_sessions : list [ SessionSnapshot ] = []
771
817
772
818
for sess in snapshot .sessions_snapshot :
773
819
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 )
776
822
777
823
if not filter_func (snapshot ) and not filtered_sessions :
778
824
return None
@@ -793,12 +839,12 @@ def filter_snapshot(
793
839
return server_copy
794
840
795
841
if isinstance (snapshot , SessionSnapshot ):
796
- filtered_windows = []
842
+ filtered_windows : list [ WindowSnapshot ] = []
797
843
798
844
for w in snapshot .windows_snapshot :
799
845
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 )
802
848
803
849
if not filter_func (snapshot ) and not filtered_windows :
804
850
return None
@@ -808,8 +854,6 @@ def filter_snapshot(
808
854
return session_copy
809
855
810
856
if isinstance (snapshot , WindowSnapshot ):
811
- filtered_panes = []
812
-
813
857
filtered_panes = [p for p in snapshot .panes_snapshot if filter_func (p )]
814
858
815
859
if not filter_func (snapshot ) and not filtered_panes :
@@ -828,15 +872,15 @@ def filter_snapshot(
828
872
829
873
830
874
def snapshot_to_dict (
831
- snapshot : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot | t .Any ,
875
+ snapshot : SnapshotType | t .Any ,
832
876
) -> dict [str , t .Any ]:
833
877
"""Convert a snapshot to a dictionary, avoiding circular references.
834
878
835
879
This is useful for serializing snapshots to JSON or other formats.
836
880
837
881
Parameters
838
882
----------
839
- snapshot : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot | Any
883
+ snapshot : SnapshotType | Any
840
884
The snapshot to convert to a dictionary
841
885
842
886
Returns
@@ -914,7 +958,7 @@ def snapshot_active_only(
914
958
"""
915
959
916
960
def is_active (
917
- obj : ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot ,
961
+ obj : SnapshotType ,
918
962
) -> bool :
919
963
"""Return True if the object is active."""
920
964
if isinstance (obj , PaneSnapshot ):
@@ -927,4 +971,4 @@ def is_active(
927
971
if filtered is None :
928
972
error_msg = "No active objects found!"
929
973
raise ValueError (error_msg )
930
- return t .cast (" ServerSnapshot" , filtered )
974
+ return t .cast (ServerSnapshot , filtered )
0 commit comments