Coverage for src/prosemark/freewriting/ports/freewrite_service.py: 100%

31 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-09-24 18:08 +0000

1"""Port interfaces for freewriting domain operations. 

2 

3This module defines the port interfaces that the freewriting domain uses 

4to interact with external systems. These ports define contracts that 

5adapters must implement. 

6""" 

7 

8from __future__ import annotations 

9 

10from abc import ABC, abstractmethod 

11from typing import TYPE_CHECKING 

12 

13if TYPE_CHECKING: # pragma: no cover 

14 from datetime import datetime 

15 

16 from prosemark.freewriting.domain.models import FreewriteSession, SessionConfig 

17 

18 

19class FreewriteServicePort(ABC): 

20 """Port interface for freewriting domain operations. 

21 

22 This port defines the contract for core freewriting operations 

23 such as session management and content handling. 

24 """ 

25 

26 @abstractmethod 

27 def create_session(self, config: SessionConfig) -> FreewriteSession: 

28 """Create a new freewriting session with given configuration. 

29 

30 Args: 

31 config: Session configuration from CLI. 

32 

33 Returns: 

34 Initialized FreewriteSession. 

35 

36 Raises: 

37 ValidationError: If configuration is invalid. 

38 FileSystemError: If target directory is not writable. 

39 

40 """ 

41 

42 @abstractmethod 

43 def append_content(self, session: FreewriteSession, content: str) -> FreewriteSession: 

44 """Append content line to the session and persist immediately. 

45 

46 Args: 

47 session: Current session state. 

48 content: Content line to append. 

49 

50 Returns: 

51 Updated session with new content and word count. 

52 

53 Raises: 

54 FileSystemError: If write operation fails. 

55 ValidationError: If content is invalid. 

56 

57 """ 

58 

59 @staticmethod 

60 @abstractmethod 

61 def validate_node_uuid(node_uuid: str) -> bool: 

62 """Validate that a node UUID is properly formatted. 

63 

64 Args: 

65 node_uuid: UUID string to validate. 

66 

67 Returns: 

68 True if valid UUID format, False otherwise. 

69 

70 """ 

71 

72 @abstractmethod 

73 def create_daily_filename(self, timestamp: datetime) -> str: 

74 """Generate filename for daily freewrite file. 

75 

76 Args: 

77 timestamp: When the session started. 

78 

79 Returns: 

80 Filename in YYYY-MM-DD-HHmm.md format. 

81 

82 """ 

83 

84 @abstractmethod 

85 def get_session_stats(self, session: FreewriteSession) -> dict[str, int | float | bool]: 

86 """Calculate current session statistics. 

87 

88 Args: 

89 session: Current session. 

90 

91 Returns: 

92 Dictionary with word_count, elapsed_time, progress metrics. 

93 

94 """ 

95 

96 

97class NodeServicePort(ABC): 

98 """Port interface for node management operations. 

99 

100 This port defines the contract for operations on prosemark nodes, 

101 including creation and content appending. 

102 """ 

103 

104 @abstractmethod 

105 def node_exists(self, node_uuid: str) -> bool: 

106 """Check if a node file exists. 

107 

108 Args: 

109 node_uuid: UUID of the node to check. 

110 

111 Returns: 

112 True if node exists, False otherwise. 

113 

114 """ 

115 

116 @abstractmethod 

117 def create_node(self, node_uuid: str, title: str | None = None) -> str: 

118 """Create a new node file and add to binder. 

119 

120 Args: 

121 node_uuid: UUID for the new node. 

122 title: Optional title for the node. 

123 

124 Returns: 

125 Path to created node file. 

126 

127 Raises: 

128 ValidationError: If UUID is invalid. 

129 FileSystemError: If creation fails. 

130 

131 """ 

132 

133 @abstractmethod 

134 def append_to_node(self, node_uuid: str, content: list[str], session_metadata: dict[str, str]) -> None: 

135 """Append freewriting content to existing node. 

136 

137 Args: 

138 node_uuid: Target node UUID. 

139 content: Lines of content to append. 

140 session_metadata: Session info for context. 

141 

142 Raises: 

143 FileSystemError: If write fails. 

144 ValidationError: If node doesn't exist. 

145 

146 """ 

147 

148 

149class FileSystemPort(ABC): 

150 """Port interface for file system operations. 

151 

152 This port defines the contract for low-level file operations 

153 that the freewriting feature needs. 

154 """ 

155 

156 @abstractmethod 

157 def write_file(self, file_path: str, content: str, append: bool = False) -> None: # noqa: FBT001, FBT002 

158 """Write content to file. 

159 

160 Args: 

161 file_path: Target file path. 

162 content: Content to write. 

163 append: Whether to append (True) or overwrite (False). 

164 

165 Raises: 

166 FileSystemError: If write operation fails. 

167 

168 """ 

169 

170 @abstractmethod 

171 def file_exists(self, file_path: str) -> bool: 

172 """Check if file exists. 

173 

174 Args: 

175 file_path: Path to check. 

176 

177 Returns: 

178 True if file exists, False otherwise. 

179 

180 """ 

181 

182 @abstractmethod 

183 def get_current_directory(self) -> str: 

184 """Get current working directory. 

185 

186 Returns: 

187 Absolute path to current directory. 

188 

189 """ 

190 

191 @abstractmethod 

192 def is_writable(self, directory_path: str) -> bool: 

193 """Check if directory is writable. 

194 

195 Args: 

196 directory_path: Directory to check. 

197 

198 Returns: 

199 True if writable, False otherwise. 

200 

201 """