Skip to content

Commit f5cd683

Browse files
committed
feat: add infrastructure layer services
- Add FileSystemService for file operations - Add GitService for Git operations - Add StateStorage for state management - Follow TDD principles for all services - Fix linting issues and type annotations
1 parent b6ee16a commit f5cd683

7 files changed

Lines changed: 545 additions & 4 deletions

File tree

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
"""
2-
Infrastructure Layer - Basic Services
3-
"""
1+
"""Infrastructure Layer - Basic Services"""
42

5-
__all__ = []
3+
from agently.infrastructure.file_system import FileSystemService
4+
from agently.infrastructure.git_service import GitService
5+
from agently.infrastructure.state_storage import StateStorage
6+
7+
__all__ = ["FileSystemService", "GitService", "StateStorage"]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""File system service for file and directory operations"""
2+
3+
from pathlib import Path
4+
5+
from agently.logging import LoggingMixin
6+
7+
8+
class FileSystemService(LoggingMixin):
9+
"""Service for file system operations"""
10+
11+
def read_file(self, file_path: Path) -> str:
12+
"""Read content from a file
13+
14+
Args:
15+
file_path: Path to the file
16+
17+
Returns:
18+
File content as string
19+
20+
Raises:
21+
FileNotFoundError: If file doesn't exist
22+
"""
23+
self.logger.info("Reading file", file_path=str(file_path))
24+
return file_path.read_text()
25+
26+
def write_file(self, file_path: Path, content: str) -> None:
27+
"""Write content to a file
28+
29+
Args:
30+
file_path: Path to the file
31+
content: Content to write
32+
"""
33+
self.logger.info("Writing file", file_path=str(file_path))
34+
file_path.write_text(content)
35+
36+
def append_file(self, file_path: Path, content: str) -> None:
37+
"""Append content to a file
38+
39+
Args:
40+
file_path: Path to the file
41+
content: Content to append
42+
"""
43+
self.logger.info("Appending to file", file_path=str(file_path))
44+
with file_path.open("a") as f:
45+
f.write(content)
46+
47+
def delete_file(self, file_path: Path) -> None:
48+
"""Delete a file
49+
50+
Args:
51+
file_path: Path to the file
52+
53+
Raises:
54+
FileNotFoundError: If file doesn't exist
55+
"""
56+
self.logger.info("Deleting file", file_path=str(file_path))
57+
file_path.unlink()
58+
59+
def file_exists(self, file_path: Path) -> bool:
60+
"""Check if a file exists
61+
62+
Args:
63+
file_path: Path to check
64+
65+
Returns:
66+
True if file exists, False otherwise
67+
"""
68+
return file_path.exists()
69+
70+
def list_directory(self, dir_path: Path) -> list[Path]:
71+
"""List files in a directory
72+
73+
Args:
74+
dir_path: Path to the directory
75+
76+
Returns:
77+
List of file paths
78+
79+
Raises:
80+
FileNotFoundError: If directory doesn't exist
81+
"""
82+
self.logger.info("Listing directory", dir_path=str(dir_path))
83+
return [f for f in dir_path.iterdir() if f.is_file()]
84+
85+
def create_directory(self, dir_path: Path) -> None:
86+
"""Create a directory
87+
88+
Args:
89+
dir_path: Path to the directory
90+
"""
91+
self.logger.info("Creating directory", dir_path=str(dir_path))
92+
dir_path.mkdir(parents=True, exist_ok=True)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Git service for Git operations"""
2+
3+
from pathlib import Path
4+
5+
from agently.logging import LoggingMixin
6+
7+
8+
class GitService(LoggingMixin):
9+
"""Service for Git operations"""
10+
11+
def init_repository(self, repo_path: Path) -> None:
12+
"""Initialize a Git repository
13+
14+
Args:
15+
repo_path: Path to the repository
16+
"""
17+
try:
18+
from git import Repo
19+
20+
self.logger.info("Initializing Git repository", repo_path=str(repo_path))
21+
Repo.init(repo_path)
22+
except ImportError as e:
23+
self.logger.error("GitPython not installed")
24+
raise ImportError("GitPython is required. Install with: pip install GitPython") from e
25+
26+
def get_current_branch(self, repo_path: Path) -> str:
27+
"""Get current branch name
28+
29+
Args:
30+
repo_path: Path to the repository
31+
32+
Returns:
33+
Branch name
34+
"""
35+
from git import Repo
36+
37+
self.logger.debug("Getting current branch", repo_path=str(repo_path))
38+
repo = Repo(repo_path)
39+
return repo.active_branch.name
40+
41+
def add_file(self, repo_path: Path, file_path: Path) -> None:
42+
"""Add a file to Git
43+
44+
Args:
45+
repo_path: Path to the repository
46+
file_path: Path to the file
47+
"""
48+
from git import Repo
49+
50+
self.logger.info("Adding file to Git", file_path=str(file_path))
51+
repo = Repo(repo_path)
52+
repo.index.add([str(file_path)])
53+
54+
def commit_changes(self, repo_path: Path, message: str) -> None:
55+
"""Commit changes
56+
57+
Args:
58+
repo_path: Path to the repository
59+
message: Commit message
60+
"""
61+
from git import Repo
62+
63+
self.logger.info("Committing changes", message=message)
64+
repo = Repo(repo_path)
65+
repo.index.commit(message)
66+
67+
def get_status(self, repo_path: Path) -> str:
68+
"""Get repository status
69+
70+
Args:
71+
repo_path: Path to the repository
72+
73+
Returns:
74+
Status string
75+
"""
76+
from git import Repo
77+
78+
self.logger.debug("Getting repository status", repo_path=str(repo_path))
79+
repo = Repo(repo_path)
80+
return str(repo.git.status())
81+
82+
def is_repository(self, repo_path: Path) -> bool:
83+
"""Check if directory is a Git repository
84+
85+
Args:
86+
repo_path: Path to check
87+
88+
Returns:
89+
True if it's a repository, False otherwise
90+
"""
91+
try:
92+
from git import Repo
93+
94+
Repo(repo_path)
95+
return True
96+
except Exception:
97+
return False
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""State storage service for managing application state"""
2+
3+
import json
4+
from pathlib import Path
5+
from typing import Any, Optional
6+
7+
from agently.logging import LoggingMixin
8+
9+
10+
class StateStorage(LoggingMixin):
11+
"""Service for state storage using SQLite"""
12+
13+
def __init__(self, db_path: Path) -> None:
14+
"""Initialize state storage
15+
16+
Args:
17+
db_path: Path to the database file
18+
"""
19+
self.db_path = db_path
20+
self._ensure_db_exists()
21+
self.logger.info("State storage initialized", db_path=str(db_path))
22+
23+
def _ensure_db_exists(self) -> None:
24+
"""Ensure database file exists"""
25+
if not self.db_path.exists():
26+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
27+
self.db_path.write_text("{}")
28+
self.logger.debug("Created new state database", db_path=str(self.db_path))
29+
30+
def _load_state(self) -> dict[str, Any]:
31+
"""Load state from database
32+
33+
Returns:
34+
State dictionary
35+
"""
36+
try:
37+
content = self.db_path.read_text()
38+
return json.loads(content)
39+
except Exception:
40+
self.logger.warning("Failed to load state, returning empty dict")
41+
return {}
42+
43+
def _save_state(self, state: dict[str, Any]) -> None:
44+
"""Save state to database
45+
46+
Args:
47+
state: State dictionary to save
48+
"""
49+
content = json.dumps(state, indent=2)
50+
self.db_path.write_text(content)
51+
52+
def store(self, key: str, value: Any) -> None:
53+
"""Store a key-value pair
54+
55+
Args:
56+
key: Storage key
57+
value: Value to store
58+
"""
59+
self.logger.debug("Storing state", key=key)
60+
state = self._load_state()
61+
state[key] = value
62+
self._save_state(state)
63+
64+
def retrieve(self, key: str) -> Optional[Any]:
65+
"""Retrieve a value by key
66+
67+
Args:
68+
key: Storage key
69+
70+
Returns:
71+
Value if exists, None otherwise
72+
"""
73+
self.logger.debug("Retrieving state", key=key)
74+
state = self._load_state()
75+
return state.get(key)
76+
77+
def delete(self, key: str) -> None:
78+
"""Delete a key-value pair
79+
80+
Args:
81+
key: Storage key to delete
82+
"""
83+
self.logger.debug("Deleting state", key=key)
84+
state = self._load_state()
85+
if key in state:
86+
del state[key]
87+
self._save_state(state)
88+
89+
def list_keys(self) -> list[str]:
90+
"""List all stored keys
91+
92+
Returns:
93+
List of keys
94+
"""
95+
self.logger.debug("Listing all keys")
96+
state = self._load_state()
97+
return list(state.keys())
98+
99+
def clear_all(self) -> None:
100+
"""Clear all stored state"""
101+
self.logger.info("Clearing all state")
102+
self._save_state({})

0 commit comments

Comments
 (0)