Skip to content

Commit dc612ac

Browse files
committed
snapshot(factory): Implement type-safe factory and fluent API
Add snapshot.factory module with type-safe create_snapshot and create_snapshot_active functions. Enhance base classes with fluent API methods like to_dict(), filter(), and active_only(). Remove exports from __init__.py per architecture plan, directing users to import from factory module directly. Add comprehensive tests for factory and fluent API methods.
1 parent 115c4b5 commit dc612ac

File tree

4 files changed

+390
-5
lines changed

4 files changed

+390
-5
lines changed

src/libtmux/snapshot/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,27 @@
88
99
This module provides hierarchical snapshots of tmux objects (Server, Session,
1010
Window, Pane) that are immutable and maintain the relationships between objects.
11+
12+
Usage
13+
-----
14+
The primary interface is through the factory functions:
15+
16+
```python
17+
from libtmux import Server
18+
from libtmux.snapshot.factory import create_snapshot, create_snapshot_active
19+
20+
# Create a snapshot of a server
21+
server = Server()
22+
snapshot = create_snapshot(server)
23+
24+
# Create a snapshot of a server with only active components
25+
active_snapshot = create_snapshot_active(server)
26+
27+
# Create a snapshot with pane content captured
28+
content_snapshot = create_snapshot(server, capture_content=True)
29+
30+
# Snapshot API methods
31+
data = snapshot.to_dict() # Convert to dictionary
32+
filtered = snapshot.filter(lambda x: hasattr(x, 'window_name')) # Filter
33+
```
1134
"""

src/libtmux/snapshot/base.py

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,124 @@
1616
from libtmux.snapshot.types import PaneT, SessionT, WindowT
1717
from libtmux.window import Window
1818

19-
20-
class SealablePaneBase(Pane, Sealable):
19+
# Forward references
20+
if t.TYPE_CHECKING:
21+
from libtmux.snapshot.models.server import ServerSnapshot
22+
from libtmux.snapshot.types import SnapshotType
23+
24+
25+
class SnapshotBase(Sealable):
26+
"""Base class for all snapshot classes.
27+
28+
This class provides common methods for all snapshot classes, such as filtering
29+
and serialization to dictionary.
30+
"""
31+
32+
_is_snapshot: bool = True
33+
34+
def to_dict(self) -> dict[str, t.Any]:
35+
"""Convert the snapshot to a dictionary.
36+
37+
This is useful for serializing snapshots to JSON or other formats.
38+
39+
Returns
40+
-------
41+
dict[str, t.Any]
42+
A dictionary representation of the snapshot
43+
44+
Examples
45+
--------
46+
>>> from libtmux import Server
47+
>>> from libtmux.snapshot.factory import create_snapshot
48+
>>> server = Server()
49+
>>> snapshot = create_snapshot(server)
50+
>>> data = snapshot.to_dict()
51+
>>> isinstance(data, dict)
52+
True
53+
"""
54+
from libtmux.snapshot.utils import snapshot_to_dict
55+
56+
return snapshot_to_dict(self)
57+
58+
def filter(
59+
self, filter_func: t.Callable[[SnapshotType], bool]
60+
) -> SnapshotType | None:
61+
"""Filter the snapshot tree based on a filter function.
62+
63+
This recursively filters the snapshot tree based on the filter function.
64+
Parent-child relationships are maintained in the filtered snapshot.
65+
66+
Parameters
67+
----------
68+
filter_func : Callable[[SnapshotType], bool]
69+
A function that takes a snapshot object and returns True to keep it
70+
or False to filter it out
71+
72+
Returns
73+
-------
74+
Optional[SnapshotType]
75+
A new filtered snapshot, or None if everything was filtered out
76+
77+
Examples
78+
--------
79+
>>> from libtmux import Server
80+
>>> from libtmux.snapshot.factory import create_snapshot
81+
>>> server = Server()
82+
>>> snapshot = create_snapshot(server)
83+
>>> # Filter to include only objects with 'name' attribute
84+
>>> filtered = snapshot.filter(lambda x: hasattr(x, 'name'))
85+
"""
86+
from libtmux.snapshot.utils import filter_snapshot
87+
88+
# This is safe at runtime because concrete implementations will
89+
# satisfy the type constraints
90+
return filter_snapshot(self, filter_func) # type: ignore[arg-type]
91+
92+
def active_only(self) -> ServerSnapshot | None:
93+
"""Filter the snapshot to include only active components.
94+
95+
This is a convenience method that filters the snapshot to include only
96+
active sessions, windows, and panes.
97+
98+
Returns
99+
-------
100+
Optional[ServerSnapshot]
101+
A new filtered snapshot containing only active components, or None if
102+
there are no active components
103+
104+
Examples
105+
--------
106+
>>> from libtmux import Server
107+
>>> from libtmux.snapshot.factory import create_snapshot
108+
>>> server = Server()
109+
>>> snapshot = create_snapshot(server)
110+
>>> active = snapshot.active_only()
111+
112+
Raises
113+
------
114+
NotImplementedError
115+
If called on a snapshot that is not a ServerSnapshot
116+
"""
117+
# Only implement for ServerSnapshot
118+
if not hasattr(self, "sessions_snapshot"):
119+
cls_name = type(self).__name__
120+
msg = f"active_only() is only supported for ServerSnapshot, not {cls_name}"
121+
raise NotImplementedError(msg)
122+
123+
from libtmux.snapshot.utils import snapshot_active_only
124+
try:
125+
# This is safe at runtime because we check for the
126+
# sessions_snapshot attribute
127+
return snapshot_active_only(self) # type: ignore[arg-type]
128+
except ValueError:
129+
return None
130+
131+
132+
class SealablePaneBase(Pane, SnapshotBase):
21133
"""Base class for sealable pane classes."""
22134

23135

24-
class SealableWindowBase(Window, Sealable, t.Generic[PaneT]):
136+
class SealableWindowBase(Window, SnapshotBase, t.Generic[PaneT]):
25137
"""Base class for sealable window classes with generic pane type."""
26138

27139
@property
@@ -35,7 +147,7 @@ def active_pane(self) -> PaneT | None:
35147
return t.cast(t.Optional[PaneT], super().active_pane)
36148

37149

38-
class SealableSessionBase(Session, Sealable, t.Generic[WindowT, PaneT]):
150+
class SealableSessionBase(Session, SnapshotBase, t.Generic[WindowT, PaneT]):
39151
"""Base class for sealable session classes with generic window and pane types."""
40152

41153
@property
@@ -54,7 +166,7 @@ def active_pane(self) -> PaneT | None:
54166
return t.cast(t.Optional[PaneT], super().active_pane)
55167

56168

57-
class SealableServerBase(Server, Sealable, t.Generic[SessionT, WindowT, PaneT]):
169+
class SealableServerBase(Server, SnapshotBase, t.Generic[SessionT, WindowT, PaneT]):
58170
"""Generic base for sealable server with typed session, window, and pane."""
59171

60172
@property

src/libtmux/snapshot/factory.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Factory functions for creating snapshots.
2+
3+
This module provides type-safe factory functions for creating snapshots of tmux objects.
4+
It centralizes snapshot creation and provides a consistent API for creating snapshots
5+
of different tmux objects.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from typing import overload
11+
12+
from libtmux.pane import Pane
13+
from libtmux.server import Server
14+
from libtmux.session import Session
15+
from libtmux.snapshot.models.pane import PaneSnapshot
16+
from libtmux.snapshot.models.server import ServerSnapshot
17+
from libtmux.snapshot.models.session import SessionSnapshot
18+
from libtmux.snapshot.models.window import WindowSnapshot
19+
from libtmux.window import Window
20+
21+
22+
@overload
23+
def create_snapshot(obj: Server, *, capture_content: bool = False) -> ServerSnapshot:
24+
...
25+
26+
27+
@overload
28+
def create_snapshot(obj: Session, *, capture_content: bool = False) -> SessionSnapshot:
29+
...
30+
31+
32+
@overload
33+
def create_snapshot(obj: Window, *, capture_content: bool = False) -> WindowSnapshot:
34+
...
35+
36+
37+
@overload
38+
def create_snapshot(obj: Pane, *, capture_content: bool = False) -> PaneSnapshot:
39+
...
40+
41+
42+
def create_snapshot(
43+
obj: Server | Session | Window | Pane, *, capture_content: bool = False
44+
) -> ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot:
45+
"""Create a snapshot of a tmux object.
46+
47+
This is a factory function that creates a snapshot of a tmux object
48+
based on its type. It provides a consistent interface for creating
49+
snapshots of different tmux objects.
50+
51+
Parameters
52+
----------
53+
obj : Server | Session | Window | Pane
54+
The tmux object to create a snapshot of
55+
capture_content : bool, optional
56+
Whether to capture the content of panes, by default False
57+
58+
Returns
59+
-------
60+
ServerSnapshot | SessionSnapshot | WindowSnapshot | PaneSnapshot
61+
A snapshot of the provided tmux object
62+
63+
Examples
64+
--------
65+
Create a snapshot of a server:
66+
67+
>>> from libtmux import Server
68+
>>> server = Server()
69+
>>> snapshot = create_snapshot(server)
70+
>>> isinstance(snapshot, ServerSnapshot)
71+
True
72+
73+
Create a snapshot of a session:
74+
75+
>>> # Get an existing session or create a new one with a unique name
76+
>>> import uuid
77+
>>> session_name = f"test-{uuid.uuid4().hex[:8]}"
78+
>>> session = server.new_session(session_name)
79+
>>> snapshot = create_snapshot(session)
80+
>>> isinstance(snapshot, SessionSnapshot)
81+
True
82+
83+
Create a snapshot with pane content:
84+
85+
>>> snapshot = create_snapshot(session, capture_content=True)
86+
>>> isinstance(snapshot, SessionSnapshot)
87+
True
88+
"""
89+
if isinstance(obj, Server):
90+
return ServerSnapshot.from_server(obj, include_content=capture_content)
91+
elif isinstance(obj, Session):
92+
return SessionSnapshot.from_session(obj, capture_content=capture_content)
93+
elif isinstance(obj, Window):
94+
return WindowSnapshot.from_window(obj, capture_content=capture_content)
95+
elif isinstance(obj, Pane):
96+
return PaneSnapshot.from_pane(obj, capture_content=capture_content)
97+
else:
98+
# This should never happen due to the type annotations
99+
obj_type = type(obj).__name__
100+
msg = f"Unsupported object type: {obj_type}"
101+
raise TypeError(msg)
102+
103+
104+
def create_snapshot_active(
105+
server: Server, *, capture_content: bool = False
106+
) -> ServerSnapshot:
107+
"""Create a snapshot containing only active sessions, windows, and panes.
108+
109+
This is a convenience function that creates a snapshot of a server and then
110+
filters it to only include active components.
111+
112+
Parameters
113+
----------
114+
server : Server
115+
The server to create a snapshot of
116+
capture_content : bool, optional
117+
Whether to capture the content of panes, by default False
118+
119+
Returns
120+
-------
121+
ServerSnapshot
122+
A snapshot containing only active components
123+
124+
Examples
125+
--------
126+
Create a snapshot with only active components:
127+
128+
>>> from libtmux import Server
129+
>>> server = Server()
130+
>>> snapshot = create_snapshot_active(server)
131+
>>> isinstance(snapshot, ServerSnapshot)
132+
True
133+
"""
134+
from libtmux.snapshot.utils import snapshot_active_only
135+
136+
server_snapshot = create_snapshot(server, capture_content=capture_content)
137+
return snapshot_active_only(server_snapshot)

0 commit comments

Comments
 (0)