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
« prev ^ index » next coverage.py v7.8.0, created at 2025-09-24 18:08 +0000
1"""Port interfaces for freewriting domain operations.
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"""
8from __future__ import annotations
10from abc import ABC, abstractmethod
11from typing import TYPE_CHECKING
13if TYPE_CHECKING: # pragma: no cover
14 from datetime import datetime
16 from prosemark.freewriting.domain.models import FreewriteSession, SessionConfig
19class FreewriteServicePort(ABC):
20 """Port interface for freewriting domain operations.
22 This port defines the contract for core freewriting operations
23 such as session management and content handling.
24 """
26 @abstractmethod
27 def create_session(self, config: SessionConfig) -> FreewriteSession:
28 """Create a new freewriting session with given configuration.
30 Args:
31 config: Session configuration from CLI.
33 Returns:
34 Initialized FreewriteSession.
36 Raises:
37 ValidationError: If configuration is invalid.
38 FileSystemError: If target directory is not writable.
40 """
42 @abstractmethod
43 def append_content(self, session: FreewriteSession, content: str) -> FreewriteSession:
44 """Append content line to the session and persist immediately.
46 Args:
47 session: Current session state.
48 content: Content line to append.
50 Returns:
51 Updated session with new content and word count.
53 Raises:
54 FileSystemError: If write operation fails.
55 ValidationError: If content is invalid.
57 """
59 @staticmethod
60 @abstractmethod
61 def validate_node_uuid(node_uuid: str) -> bool:
62 """Validate that a node UUID is properly formatted.
64 Args:
65 node_uuid: UUID string to validate.
67 Returns:
68 True if valid UUID format, False otherwise.
70 """
72 @abstractmethod
73 def create_daily_filename(self, timestamp: datetime) -> str:
74 """Generate filename for daily freewrite file.
76 Args:
77 timestamp: When the session started.
79 Returns:
80 Filename in YYYY-MM-DD-HHmm.md format.
82 """
84 @abstractmethod
85 def get_session_stats(self, session: FreewriteSession) -> dict[str, int | float | bool]:
86 """Calculate current session statistics.
88 Args:
89 session: Current session.
91 Returns:
92 Dictionary with word_count, elapsed_time, progress metrics.
94 """
97class NodeServicePort(ABC):
98 """Port interface for node management operations.
100 This port defines the contract for operations on prosemark nodes,
101 including creation and content appending.
102 """
104 @abstractmethod
105 def node_exists(self, node_uuid: str) -> bool:
106 """Check if a node file exists.
108 Args:
109 node_uuid: UUID of the node to check.
111 Returns:
112 True if node exists, False otherwise.
114 """
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.
120 Args:
121 node_uuid: UUID for the new node.
122 title: Optional title for the node.
124 Returns:
125 Path to created node file.
127 Raises:
128 ValidationError: If UUID is invalid.
129 FileSystemError: If creation fails.
131 """
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.
137 Args:
138 node_uuid: Target node UUID.
139 content: Lines of content to append.
140 session_metadata: Session info for context.
142 Raises:
143 FileSystemError: If write fails.
144 ValidationError: If node doesn't exist.
146 """
149class FileSystemPort(ABC):
150 """Port interface for file system operations.
152 This port defines the contract for low-level file operations
153 that the freewriting feature needs.
154 """
156 @abstractmethod
157 def write_file(self, file_path: str, content: str, append: bool = False) -> None: # noqa: FBT001, FBT002
158 """Write content to file.
160 Args:
161 file_path: Target file path.
162 content: Content to write.
163 append: Whether to append (True) or overwrite (False).
165 Raises:
166 FileSystemError: If write operation fails.
168 """
170 @abstractmethod
171 def file_exists(self, file_path: str) -> bool:
172 """Check if file exists.
174 Args:
175 file_path: Path to check.
177 Returns:
178 True if file exists, False otherwise.
180 """
182 @abstractmethod
183 def get_current_directory(self) -> str:
184 """Get current working directory.
186 Returns:
187 Absolute path to current directory.
189 """
191 @abstractmethod
192 def is_writable(self, directory_path: str) -> bool:
193 """Check if directory is writable.
195 Args:
196 directory_path: Directory to check.
198 Returns:
199 True if writable, False otherwise.
201 """