Skip to content

Commit ec6db9b

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 91b57f7 commit ec6db9b

File tree

4 files changed

+394
-5
lines changed

4 files changed

+394
-5
lines changed

src/libtmux/snapshot/__init__.py

+23
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

+118-5
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,125 @@
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+
125+
try:
126+
# This is safe at runtime because we check for the
127+
# sessions_snapshot attribute
128+
return snapshot_active_only(self) # type: ignore[arg-type]
129+
except ValueError:
130+
return None
131+
132+
133+
class SealablePaneBase(Pane, SnapshotBase):
21134
"""Base class for sealable pane classes."""
22135

23136

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

27140
@property
@@ -35,7 +148,7 @@ def active_pane(self) -> PaneT | None:
35148
return t.cast(t.Optional[PaneT], super().active_pane)
36149

37150

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

41154
@property
@@ -54,7 +167,7 @@ def active_pane(self) -> PaneT | None:
54167
return t.cast(t.Optional[PaneT], super().active_pane)
55168

56169

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

60173
@property

src/libtmux/snapshot/factory.py

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

0 commit comments

Comments
 (0)